Is_convertible для нескольких аргументов

Предположим, что у меня нет std::is_convertible по какой-либо причине и я хочу реализовать его сам. В стандарте говорится следующее:

Условие предиката для специализированной специализации is_convertible<From, To> должно выполняться тогда и только тогда, когда выражение return в следующем коде будет правильно сформировано, включая любые неявные преобразования в возвращаемый тип функции:

To f() {
    return declval<From>();
}

Хорошо, неважно, я могу сделать это так (обратите внимание на порядок аргументов, противоположный порядку в std::is_convertible, это преднамеренно и не имеет отношения к проблеме):

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;
};

Это, кажется, работает по назначению, и, насколько я могу судить, делает то же самое.

Теперь я могу различать неявные и явные (или вообще ничего) конструкторы:

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;
}

До сих пор так хорошо. Теперь я хочу сделать то же самое с несколькими конструкторами аргументов. Это не имело смысла до c++11, поскольку не было никакого способа вызвать вызов конструктора multiargument неявно. Но теперь у нас есть синтаксис списка инициализаторов, заключенный в скобки, и ключевое слово explicit в конструкторе многоаргументов имеет значение.

Расширим определение:

Условие предиката для специализированной специализации my_is_convertible_many<To, From...> должно выполняться тогда и только тогда, когда выражение return в следующем коде будет хорошо сформировано, включая любые неявные преобразования в возвращаемый тип функции:

To f() {
    return {declval<From>()...};
}

Чтобы реализовать это, я пошел очевидным путем:

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;
};

Это правильно сообщает true при наличии совпадающего неявного конструктора и false, если он не является конструктором matcing. Но он не скомпилируется, если существует явный конструктор сопоставления (по крайней мере, на 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;
}

Ошибка заключается в попытке неявно вызвать явный конструктор, который на gcc звучит так:

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());
                                                                                                 ^

Это разумно. Однако я ожидаю, что эта перегрузка test исчезнет, ​​а другая будет использоваться вместо этого, не создавая ошибок.

Итак, вопрос: почему этого не происходит, и что я могу сделать с ним?

Ответы

Ответ 1

gcc недавно исправлено, а версия 4.9 примет код. Кланг тоже принимает его, поэтому код, вероятно, прекрасен. Это не говорит вам, как обойти проблему со старыми версиями gcc, извините.