C++ SFINAE с decltype: ошибка подстановки становится ошибкой?
этот код работает:
// Code A
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename T>
struct S {
template <typename Iter, typename = typename enable_if<is_constructible<T, decltype(*(declval<Iter>()))>::value>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin()); // stdout: S(Iter)
S<int> s2(1); // stdout: S(int)
}
но этот код ниже не работает. В приведенном ниже коде я просто хочу наследовать std::enable_if
, Так что класс is_iter_of
будет иметь член typedef type
если выбран вариант std::enable_if
имеет член typedef type
.
// Code B
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename Iter, typename Target>
struct is_iter_of : public enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value> {}
template <typename T>
struct S {
template <typename Iter, typename = typename is_iter_of<Iter, T>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin());
S<int> s2(1); // this is line 22, error
}
сообщение об ошибке:
In instantiation of 'struct is_iter_of<int, int>':
12:30: required by substitution of 'template<class Iter, class> S<T>::S(Iter) [with Iter = int; <template-parameter-1-2> = <missing>]'
22:16: required from here
8:72: error: invalid type argument of unary '*' (have 'int')
сообщение об ошибке сбивает с толку: конечно, я хочу, чтобы замена шаблона не удалась.. таким образом, можно выбрать правильный конструктор. Почему SFINAE не работает в Code B
? Если invalid type argument of unary '*' (have 'int')
оскорбляет компилятор, компилятор должен был выдать ту же ошибку для Code A
как хорошо.
2 ответов
дело в том, что вы пытаетесь расширить от std::enable_if
, но выражение, которое вы помещаете в enable if, может быть недопустимым. Поскольку вы используете класс, который наследует форму, класс, который вы создаете, наследуется от недопустимого выражения, следовательно, ошибка.
простое решение для того, чтобы иметь имя enable_if
выражение будет использовать псевдоним вместо класс:
template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;
SFINAE по-прежнему будет работать, как ожидалось с псевдонимом.
это поскольку создание экземпляра псевдонима является частью функции, на которую вы пытаетесь применить SFINAE. С наследованием, выражение является частью класса instanciated, а не функция. Вот почему вы получили жесткую ошибку.
дело в том, что в вашем случае применяется несколько способов SFINAE. Давайте посмотрим, где SFINAE может произойти:
enable_if< // here -------v
is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
// ^--- here
действительно, SFINAE произойдет потому что enable_if::type
не будет существовать, если параметр bool равен false, вызывая SFINAE.
но если вы посмотрите внимательно, другой тип может не существовать:decltype(*(std::declval<Iter>()))
. Если Iter
is int
запрос типа Звездный оператора нет смысла. Так что SFINAE, если применяется там тоже.
ваше решение с наследованием будет работать, если каждый класс, который будет отправлять как Iter
имел *
оператора. С int
он не существует, вы отправляете несуществующий тип в std::is_constructible
, что делает недопустимым все выражение, формирующее базовый класс.
С псевдонимом, все выражение использования std::enable_if
подлежит применению SFINAE. В то время как подход базового класса будет применяться только SFINAE на результат std::enable_if
.
проблема в том, что выражение *int
(*(declval<Iter>())
) недопустимо, поэтому ваш шаблон завершается ошибкой. Вам нужен другой уровень шаблонов, поэтому я предлагаю подход void_t:
сделать
is_iter_of
концепцией-lite, которая происходит отtrue_type
илиfals_type
использовать
enable_if
в определении вашего класса, чтобы включить конструктор итератора.
главное понять, что ваш конструктор раньше нужен был тип для typename is_iter_of<Iter, T>::type
кроме того, что ваши enable_if
на struct is_iter_of
из-за чего все это было плохо сформировано. И поскольку не было откатного шаблона, у вас была ошибка компилятора.
template<class...>
using voider = void;
template <typename Iter, typename Target, typename = void>
struct is_iter_of : std::false_type{};
template <typename Iter, typename Target>
struct is_iter_of<Iter, Target, voider<decltype(*(declval<Iter>()))>> : std::is_constructible<Target, decltype(*(declval<Iter>()))> {};
template <typename T>
struct S {
template <typename Iter, typename std::enable_if<is_iter_of<Iter, T>::value, int>::type = 0>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
демо (C++11)
что происходит
дополнительные voider
делает специализацию шаблона не предпочтительной, если *(declval<Iter>())
- это плохо сформированные выражения (*int
) и поэтому резервный базовый шаблон (std::false_type
) является выбранный.
Else, он будет получен из std::is_constructible``. In other words, it can still derive from
std:: false_typeif the expression is well-formed but it's not constructibe, and
true_type` в противном случае.