Suppose I don't have std::is_convertible
for whatever reason and want to implement it myself. The standard says something along these lines:
The predicate condition for a template specialization
is_convertible<From, To>
shall be satisfied if and only if the return expression in the following code would be well-formed, including any implicit conversions to the return type of the function:To f() { return declval<From>(); }
Okay, no big deal, I can do it like this (Note argument order opposite to the one in std::is_convertible
, this is intentional and irrelevant to the issue):
template <typename To_, typename From_>
class my_is_convertible {
private:
template <typename To>
struct indirector {
indirector(To);
};
template <typename To, typename From>
struct tag {};
template <typename To, typename From>
static auto test(tag<To, From>)
-> decltype(indirector<To>(std::declval<From>()), std::true_type());
static auto test(...)
-> std::false_type;
public:
static constexpr bool value = decltype(test(tag<To_, From_>()))::value;
};
This seems to work as intended, and as far as I can tell does the same thing.
Now I can distinguish between implicit and explicit (or none at all) constructors:
struct A {};
struct B {};
struct Test {
Test(A);
explicit Test(B);
};
int main() {
std::cout << my_is_convertible<Test, A>::value; // true
std::cout << my_is_convertible<Test, B>::value; // false
return 0;
}
So far, so good. Now, I want to do the same with multiple argument constructors. This didn't make sense prior to c++11
as there was no way to call multiargument constructor implicitly. But now we have brace enclosed initializer list syntax, and explicit
keyword on multiargument constructor makes a difference.
Let us extend the definition:
The predicate condition for a template specialization
my_is_convertible_many<To, From...>
shall be satisfied if and only if the return expression in the following code would be well-formed, including any implicit conversions to the return type of the function:To f() { return {declval<From>()...}; }
To implement it I went the obvious way:
template <typename To_, typename... From_>
class my_is_convertible_many {
private:
template <typename To>
struct indirector {
indirector(To);
};
template <typename To, typename... From>
struct tag {};
template <typename To, typename... From>
static auto test(tag<To, From...>)
-> decltype(indirector<To>({std::declval<From>()...}), std::true_type());
static auto test(...)
-> std::false_type;
public:
static constexpr bool value = decltype(test(tag<To_, From_...>()))::value;
};
This correctly reports true
in the presence of matching implicit constructor and false
if the is no matcing constructor. But it fails to compile if there is an explicit matching constructor (at least on gcc 4.8.1):
struct A {};
struct B {};
struct C {};
struct Test {
Test(A, A);
//Test(B, B);
explicit Test(C, C);
};
int main() {
std::cout << my_is_convertible_many<Test, A, A>::value; // true, correct
std::cout << my_is_convertible_many<Test, B, B>::value; // false, correct
std::cout << my_is_convertible_many<Test, C, C>::value; // error
return 0;
}
The error is about attempting to implicitly call explicit constructor, which on gcc sounds like this:
main.cpp: In substitution of 'template<class To, class ... From> static decltype (((my_is_convertible_many<To_, From_>::indirector<To>)({(declval<From>)()...}), std::true_type())) my_is_convertible_many<To_, From_>::test(my_is_convertible_many<To_, From_>::tag<To, From ...>) [with To = To; From = {From ...}; To_ = Test; From_ = {C, C}] [with To = Test; From = {C, C}]':
main.cpp:21:73: required from 'constexpr const bool my_is_convertible_many<Test, C, C>::value'
main.cpp:37:54: required from here
main.cpp:17:97: error: converting to 'Test' from initializer list would use explicit constructor 'Test::Test(C, C)'
static auto test(tag<To, From...>) -> decltype(indirector<To>({std::declval<From>()...}), std::true_type());
^
Which is sensible. I, however, expect this overload of test
to sfinae out and the other one to be used instead, thus producing no error.
So the question is: Why doesn't this happen, and what can I do about it?
gcc was recently patched and version 4.9 will accept the code. clang accepts it as well, so the code is probably fine. That doesn't tell you how to work around the issue with older versions of gcc, sorry.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With