Background: I'm writing a wrapper type like Either<A, B>
, and I'd like return {some, args};
to work from a function returning Either<A, B>
exactly when it'd work from a function returning A
or B
. However, I also want to detect when both A
and B
could be initialized with {some, args}
, and produce an error to protect users from the ambiguity.
To detect whether a type T
can be initialized from some arguments, I tried writing a function like this:
template<typename T, typename... Args>
auto testInit(Args&&... args) -> decltype(T{std::forward<Args>(args)...});
// imagine some other fallback overload here...
I thought the expression testInit<T>(some, args)
should be valid exactly when T{some, args}
is valid — in the code below, the initialization auto x = MyType{1UL, 'a'};
works, and this test also passes:
struct MyType {
MyType(size_t a, char b) {}
};
auto x = MyType{1UL, 'a'}; // ok
static_assert(std::is_same<MyType, decltype(testInit<MyType>(1UL, 'a'))>::value, ""); // ok
However, when we add a constructor from std::initializer_list<char>
, it breaks:
struct MyType {
MyType(size_t a, char b) {}
MyType(std::initializer_list<char> x) {} // new!
};
auto x = MyType{1UL, 'a'}; // still ok
// FAILS:
static_assert(std::is_same<MyType, decltype(testInit<MyType>(1UL, 'a'))>::value, "");
note: candidate template ignored: substitution failure [with T = MyType, Args = <unsigned long, char>]: non-constant-expression cannot be narrowed from type 'unsigned long' to 'char' in initializer list
auto testInit(Args&&... args) -> decltype(T{std::forward<Args>(args)...}); ^ ~~~
Why is Clang refusing to resolve my (size_t, char)
constructor in favor of the initializer_list
constructor? How can I correctly detect whether return {some, args};
would work in a function returning T
, regardless of whether it's an aggregate type, a user-defined constructor, or an initializer_list
constructor?
return {some, args};
would work in a function returningT
?" – Teodoor