sfinae上下文中的模板变量(Template variable in sfinae context)

请考虑第一段代码,其中使用基本SFINAE触发器来区分类型是否是随机访问迭代器:

template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value;

此代码编译并按预期工作。 现在,考虑这个第二个片段,其中我将模板变量替换为enable_if条件,根本不改变它的定义:

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>; template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< //std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> has_random_access_iterator_tag<T> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value;

SFINAE不再工作,编译器(用gcc 8和clang 7测试)抱怨std::iterator_traits不存在,只要我提供一个不专门用于的类型。

这是一个工作示例:

#include <iostream> #include <vector> #include <iterator> #include <type_traits> template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>; template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< //std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> has_random_access_iterator_tag<T> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value; int main() { std::cout << std::boolalpha << "Is random access iterator:\n" << "- int: " << is_random_access_iterator_v<int> << '\n' << "- int*: " << is_random_access_iterator_v<int*> << '\n' << "- v::it: " << is_random_access_iterator_v<std::vector<int>::iterator> << '\n'; }

输出:

prog.cc:8:54:错误:'std :: __ 1 :: iterator_traits'std :: is_same_v :: iterator_category,std :: random_access_iterator_tag>中没有名为'iterator_category'的类型; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~ 〜

prog.cc:17:9:注意:在这里请求的变量模板专门化'has_random_access_iterator_tag'的实例化has_random_access_iterator_tag ^

prog.cc:22:46:注意:在类模板部分特化的模板参数推导期间is_random_access_iterator>'[with T = int] constexpr bool is_random_access_iterator_v = is_random_access_iterator :: value; ^

prog.cc:22:46:注意:在这里请求的模板类'is_random_access_iterator'的实例中prog.cc:26:35:注意:在这里请求的变量模板专用化'is_random_access_iterator_v'的实例化中<<“ - int:”<< << is_random_access_iterator_v <<'\ n'^ 1错误生成。

任何人都能解释我为什么? 我对此感到沮丧,因为使用模板常量让模板编程更加紧凑和可读性感觉非常自然。

Please consider this first snippet of code, in which a basic SFINAE trigger is used to discriminate whether a type is a random access iterator:

template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value;

This code compiles and works just as expected. Now, consider this second snippet, where I substituted a template variable to the enable_if condition, without changing its definition at all:

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>; template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< //std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> has_random_access_iterator_tag<T> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value;

SFINAE doesn't work anymore and the compiler (tested with gcc 8 and clang 7) complains about std::iterator_traits not existing whenever I supply a type it isn't specialized for.

Here's a working example:

#include <iostream> #include <vector> #include <iterator> #include <type_traits> template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>; template <typename T, typename = void> struct is_random_access_iterator : public std::false_type {}; template <typename T> struct is_random_access_iterator<T, std::enable_if_t< //std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag> has_random_access_iterator_tag<T> >> : public std::true_type {}; template <typename T> constexpr bool is_random_access_iterator_v = is_random_access_iterator<T>::value; int main() { std::cout << std::boolalpha << "Is random access iterator:\n" << "- int: " << is_random_access_iterator_v<int> << '\n' << "- int*: " << is_random_access_iterator_v<int*> << '\n' << "- v::it: " << is_random_access_iterator_v<std::vector<int>::iterator> << '\n'; }

And the output:

prog.cc:8:54: error: no type named 'iterator_category' in 'std::__1::iterator_traits' std::is_same_v::iterator_category, std::random_access_iterator_tag>; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~

prog.cc:17:9: note: in instantiation of variable template specialization 'has_random_access_iterator_tag' requested here has_random_access_iterator_tag ^

prog.cc:22:46: note: during template argument deduction for class template partial specialization 'is_random_access_iterator > >' [with T = int] constexpr bool is_random_access_iterator_v = is_random_access_iterator::value; ^

prog.cc:22:46: note: in instantiation of template class 'is_random_access_iterator' requested here prog.cc:26:35: note: in instantiation of variable template specialization 'is_random_access_iterator_v' requested here << "- int: " << is_random_access_iterator_v << '\n' ^ 1 error generated.

Can anyone explain me why? I'm frustrated by this because it feels very natural to use a template constant to make template programming more compact and readable.

最满意答案

这里的问题是

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

移动

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>

脱离SFINAE上下文并将其放入编译代码中。 SFINAE在将推导类型替换为模板参数时发生故障,因为重载解析期间出现故障。 以来

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

只有T作为模板参数,当您尝试实例化has_random_access_iterator_tag<T>时没有失败。 因为那时没有失败

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

得到编译,如果T对该表达式无效,则会出现错误。

如果将表达式移回模板参数中,那么当编译器推断T ,它将使用它来推断

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

如果失败了,那么它不会是一个错误,因为它在模板参数的扣除过程中发生了。 所以,如果我们有

template <typename T, bool Val = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>> constexpr bool has_random_access_iterator_tag = Val;

那么如果std::iterator_traits<T>::iterator_category无效,则整个模板被认为无效并被忽略。

The issue here is that

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

Moves

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>

out of a SFINAE context and puts it into just compiled code. SFINAE kicks when when substituting the deduced types for a template parameter fails durring overload resolution. Since

template <typename T> constexpr bool has_random_access_iterator_tag = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

only has T as a template parameter there is no failure when you try and instantiate a has_random_access_iterator_tag<T>. Since there is no failure then

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

gets compiled and if T is not valid for that expression then you get an error.

If you move the expression back into the template parameter then when the compiler deduces T it will use it to deduce

std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>;

and if that fails then it wont be an error since it happened durring the deduction of the template parameters. So if we have

template <typename T, bool Val = std::is_same_v<typename std::iterator_traits<T>::iterator_category, std::random_access_iterator_tag>> constexpr bool has_random_access_iterator_tag = Val;

then if std::iterator_traits<T>::iterator_category is invalid the whole template is considered invalid and ignored.

更多推荐