title | document | date | audience | author | toc | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
`visit<R>`: Explicit Return Type for `visit` |
P0655R0 |
2017-10-14 |
Library Evolution Group |
|
false |
This paper proposes to allow visiting variant
s with an explicitly specified
return type.
Variant visitation requires invocation of all combinations of alternatives to
result in the same type, such type is deduced as the visitation return type.
It is sometimes desirable to explicitly specify a return type to which all
the invocations are implicitly convertible to, as if by INVOKE
<R>
rather
than INVOKE
:
struct process {
template <typename I>
auto operator()(I i) -> O<I> { /* ... */ };
};
std::variant<I1, I2> input = /* ... */;
// mapping from a `variant` of inputs to a `variant` of results:
auto output = std::visit<std::variant<O<I1>, O<I2>>>(process{}, input);
// coercing different results to a common type:
auto result = std::visit<std::common_type_t<O<I1>, O<I2>>>(process{}, input);
// visiting a `variant` for the side-effects, discarding results:
std::visit<void>(process{}, input);
In all of the above cases the return type deduction would have failed, as each invocation yields a different type for each alternative.
This proposal is a pure library extension.
Modify §23.7.2 [variant.syn] of [@N4687] as indicated:
// 23.7.7, visitation
template <class Visitor, class... Variants>
constexpr @_see below_@ visit(Visitor&&, Variants&&...);
+ template <class R, class Visitor, class... Variants>
+ constexpr R visit(Visitor&&, Variants&&...);
Add new paragraphs to §23.7.7 [variant.visit] of [@N4687]:
+ template <class R, class Visitor, class... Variants> + constexpr R visit(Visitor&& vis, Variants&&... vars);::: add
Requires: The expression in the Effects: element shall be a valid expression for all combinations of alternative types of all variants. Otherwise, the program is ill-formed.
Effects: Let
is...
bevars.index()...
. ReturnsINVOKE
<R>(forward<Visitor>(vis), get<is>(forward<Variants>(vars))...);
.Throws:
bad_variant_access
if anyvariant
invars
isvalueless_by_exception()
.Complexity: For
sizeof...(Variants) <= 1
, the invocation of the callable object is implemented in constant time, i.e. it does not depend onsizeof...(Types)
. Forsizeof...(Variants) > 1
, the invocation of the callable object has no complexity requirements. :::
There is a corner case for which the new overload could clash with the existing
overload. A call to std::visit<Result>
actually performs overload resolution
with the following two candidates:
template <class Visitor, class... Variants>
constexpr decltype(auto) visit(Visitor&&, Variants&&...);
template <class R, class Visitor, class... Variants>
constexpr R visit(Visitor&&, Variants&&...);
The template instantiation via std::visit<Result>
replaces Visitor
with
Result
for the first overload, R
with Result
for the second, and
we end up with the following:
template <class... Variants>
constexpr decltype(auto) visit(Result&&, Variants&&...);
template <class Visitor, class... Variants>
constexpr Result visit(Visitor&&, Variants&&...);
This results in an ambiguity if Result&&
happens to be the same type as
Visitor&&
. For example, a call to std::visit<Vis>(Vis{});
would be
ambiguous since Result&&
and Visitor&&
are both Vis&&
.
In general, we would first need a self-returning visitor, then an invocation
to std::visit
with the same type and value category specified for
the return type and the visitor argument.
We claim that this problem is not worth solving considering the rarity of such a use case and the complexity of a potential solution.
Finally, note that this is not a new problem since bind
already uses
the same pattern to support bind<R>
:
template <class F, class... BoundArgs>
@_unspecified_@ bind(F&&, BoundArgs&&...);
template <class R, class F, class... BoundArgs>
@_unspecified_@ bind(F&&, BoundArgs&&...);
MPark.Variant
implementsvisit<R>
as proposed in thevisit-r
branch.Eggs.Variant
has provided an implementation ofvisit<R>
asapply<R>
since 2014, and also handles the corner case mentioned in Design Decisions.
There are other similar facilities that currently use INVOKE
, and
do not provide an accompanying overload that uses INVOKE
<R>
.
Some examples are std::invoke
, std::apply
, and std::async
.
There may be room for a paper with clear guidelines as to if/when such facilities should have an accompanying overload.
references:
- id: N4687 citation-label: N4687 title: "Working Draft, Standard for Programming Language C++" issued: year: 2017 URL: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4687.pdf