convert()
utility functionA few recent threads have expected either return ptr;
(Bjarne Stroustrup in c++std-ext-14038) or
foo({ptr})
(https://groups.google.com/a/isocpp.org/d/topic/std-proposals/YBnWHvd_dIY/discussion)
to invoke the explicit unique_ptr(T*)
constructor. Both
threads eventually proposed a list of language changes to make the necessary
conversion more concise:
auto{ptr}
explicit listinit unique_ptr(pointer
p)
to allow {ptr}
to work.return explicit({ new foo });
auto return ptr;
explicit return x
or return explicit x
return
statement. (By analogy to the rule for move constructors.)In c++std-ext-14049 and independently in https://groups.google.com/a/isocpp.org/d/msg/std-proposals/YBnWHvd_dIY/2iBLM-fAqngJ, I and Vicente J. Botet Escriba proposed a library solution to handle this problem with a single extra token:
template<typename T>
struct Converter {
T source_;
Converter(T source) : source_(std::forward<T>(source)) {}
template<typename U, decltype(static_cast<U>(std::forward<T>(source_)), 1)=0>
/*implicit*/ operator U() {
return static_cast<U>(std::forward<T>(source_));
}
};
template<typename T>
Converter<T> convert(T&& source) {
return Converter<T>(std::forward<T>(source));
}
Where an explicit conversion is desired, the user needs to write
convert(ptr)
in a context that expects a particular type.
It's worth calling out some features of this implementation:
convert()
uses a T&&
parameter to deduce the
rvalue/const-ness of the argument, and then saves that exact type into the
returned Converter
object. This allows the
Converter
to perfectly forward the argument into the target
explicit conversion.The Converter
object appears
to store the source object by value, but
because the deduced parameter is passed in, it actually adapts to the
exact type of the argument. If the argument was an lvalue reference,
source_
is an lvalue reference to convert()
's
argument. If the argument was an rvalue reference or temporary, it's
moved into the Converter
, and then moved again into the
explicit conversion. Implementations can optimize this into a single move
by specializing Converter
separately for lvalue and rvalue
arguments. Trying to capture an rvalue reference directly would make
auto c = convert(Temporary());
unsafe.
The implicit conversion operator is removed from the overload set if there's no explicit conversion to the requested type. This allows overload resolution to work as expected:
struct Explicit {};
struct Unrelated{};
struct Source {
explicit operator Explicit() const;
};
void overloaded(Explicit);
void overloaded(Unrelated);
void test() {
overloaded(convert(Source())); // Selects overloaded(Explicit).
}
tuple<>
's constructor, but LWG Issue
2051 proposes fixing that problem by making tuple<>
's
constructor implicit, and I don't know of many other use cases. Further,
convert(arg1, arg2, arg3)
is more different from {arg1,
arg2, arg3}
than convert(arg)
is from
arg
. Therefore, I leave this possibility to a future
extension.The example from c++std-ext-14038 is easily handled:
unique_ptr<Shape> read_shape(int i)
{
switch(i) {
case 1: return unique_ptr<Shape>(new Circle);
case 2: return convert(new Triangle);
}
}
The proposed std::split()
function yields another use case. We'd like the returned range to be less
magic, so it's likely to be a simple range of std::string_ref
,
which is only explicitly convertible to std::string
. If users
want to initialize a vector<string>
from a
split()
call, and we adopt a range library like Boost.Range, the constructors
from N3456,
and generic
lambdas, they'd write something like:
std::vector<std::string> v =
std::split(input) | transformed([](auto s) std::string(s));
This isn't too bad, but convert()
helps us shorten it for
everything after the first use:
auto converted = transformed([](auto v) std::convert(v)); // Define once, globally.
std::vector<std::string> v = std::split(input) | converted;
Wording is relative to N3485.
// [utility.convert] convert:
template <class T> see below convert(T&& source);
template <class T> see below convert(T&& source);
Requires: If T&&
is an rvalue reference type, T
shall be MoveConstructible
([moveconstructible]).
Returns: An object with a member of type T
that is
constructed from forward<T>(source)
. For exposition only,
this member is named source_
. [ Note: This
member has reference type if source
was passed an lvalue. It
only implies a move if source
was passed an rvalue. -- end
note ]
Remarks:
T
is either implicitly or explicitly convertible
to.U
shall be implemented
equivalently to
static_cast<U>(std::forward<T>(source_))
.CopyConstructible
if
either T
is an lvalue reference type or T
is
CopyConstructible
.