Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is_convertible for multiple arguments

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?

like image 587
yuri kilochek Avatar asked Dec 31 '13 16:12

yuri kilochek


1 Answers

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.

like image 159
Marc Glisse Avatar answered Oct 02 '22 21:10

Marc Glisse