Ответ 1
gcc недавно исправлено, а версия 4.9 примет код. Кланг тоже принимает его, поэтому код, вероятно, прекрасен. Это не говорит вам, как обойти проблему со старыми версиями gcc, извините.
Предположим, что у меня нет 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
исчезнет, а другая будет использоваться вместо этого, не создавая ошибок.
Итак, вопрос: почему этого не происходит, и что я могу сделать с ним?
gcc недавно исправлено, а версия 4.9 примет код. Кланг тоже принимает его, поэтому код, вероятно, прекрасен. Это не говорит вам, как обойти проблему со старыми версиями gcc, извините.