首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SFINAE未能处理中间类型性状

SFINAE未能处理中间类型性状
EN

Stack Overflow用户
提问于 2018-08-29 16:21:55
回答 3查看 77关注 0票数 1

考虑以下测试代码:

代码语言:javascript
复制
// Preprocessor
#include <iostream>
#include <type_traits>

// Structure with no type alias
template <class T>
struct invalid {
};

// Structure with a type alias
template <class T>
struct valid {
    using type = T;
};

// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
    using type = typename T::type;
};

// One argument function
template <class T, class = typename traits<T>::type>
void function(T) {
    std::cout << "function(T)" << std::endl;
}

// Two arguments function
template <class T, class U, class = typename traits<T, U>::type>
void function(T, U) {
    std::cout << "function(T, U)" << std::endl;
}

// When function can be called on all arguments
template <
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(Args&&...)" << std::endl;
}

// When function can be called on all arguments except the first one
template <
    class T,
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}

// Main function
int main(int argc, char* argv[]) {
    valid<int> v;
    invalid<int> i;
    sfinae(v);
    sfinae(i, v);
    return 0;
}

该守则包括:

  • 没有invalid的结构::type
  • 具有一个valid的结构::type
  • traits定义为T::type的结构T::type
  • 重载的function,只有在定义了traits<T>::type的第一个参数的类型时才能工作。
  • 一个重载的sfinae函数,即使第一个参数是invalid,也应该能够调用invalid

然而,SFINAE机制在这种情况下似乎不起作用,我也不确定为什么。错误如下:

代码语言:javascript
复制
sfinae_problem_make.cpp:19:30: error: no type named 'type' in 'invalid<int>'
    using type = typename T::type;
                 ~~~~~~~~~~~~^~~~
sfinae_problem_make.cpp:29:46: note: in instantiation of template class 'traits<invalid<int>, valid<int> >' requested here
template <class T, class U, class = typename traits<T, U>::type>
                                             ^
sfinae_problem_make.cpp:30:6: note: in instantiation of default argument for 'function<invalid<int>, valid<int> >' required here
void function(T, U) {
     ^~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:37:22: note: while substituting deduced template arguments into function template 'function' [with T = invalid<int>, U = valid<int>, $2 = (no value)]
    class = decltype(function(std::declval<Args>()...))
                     ^
sfinae_problem_make.cpp:39:6: note: in instantiation of default argument for 'sfinae<invalid<int> &, valid<int> &>' required here
void sfinae(Args&&... args) {
     ^~~~~~~~~~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:60:5: note: while substituting deduced template arguments into function template 'sfinae' [with Args = <invalid<int> &, valid<int> &>, $1 = (no value)]
    sfinae(i, v);

最令人惊讶的是,如果从问题中去掉特征:

代码语言:javascript
复制
// Preprocessor
#include <iostream>
#include <type_traits>

// Structure with no type alias
template <class T>
struct invalid {
};

// Structure with a type alias
template <class T>
struct valid {
    using type = T;
};

// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
    using type = typename T::type;
};

// One argument function
template <class T, class = typename T::type>
void function(T) {
    std::cout << "function(T)" << std::endl;
}

// Two arguments function
template <class T, class U, class = typename T::type>
void function(T, U) {
    std::cout << "function(T, U)" << std::endl;
}

// When function can be called on all arguments
template <
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(Args&&...)" << std::endl;
}

// When function can be called on all arguments except the first one
template <
    class T,
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}

// Main function
int main(int argc, char* argv[]) {
    valid<int> v;
    invalid<int> i;
    sfinae(v);
    sfinae(i, v);
    return 0;
}

然后,它按预期工作并输出:

代码语言:javascript
复制
function(T)
sfinae(Args&&...)
function(T)
sfinae(const invalid<T>&, Args&&...)

问题:为什么第一个版本不能工作,是否有办法使它与中间类型的特性一起工作?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-08-29 16:41:29

从根本上讲,这归结为“即时上下文”在[temp.deduct]/8中的含义,即sfinae规则,它没有得到非常明确的定义(参见cwg 1844):

如果替换导致类型或表达式无效,则类型扣减失败。无效类型或表达式是格式错误的类型或表达式,如果使用替换的参数编写,则需要进行诊断。[ 注:如果不需要诊断,程序仍然是不正确的.访问检查是替代过程的一部分。- end注意到仅在函数类型的直接上下文中,其模板参数类型及其显式说明符中的无效类型和表达式可能导致扣减失败。[ 注意:将类型和表达式替换为类型和表达式可能会产生诸如类模板专门化和/或函数模板专门化的实例化、隐式定义函数的生成等效果。这些效果不是在“即时上下文”中发生的,可能导致程序错误形成。- end注意事项 ]

在这种情况下,可以说直接的上下文就是看到traits<T,U>::type是一个存在的东西。它确实是这样。但是,只有当我们将该类型作为默认参数进行实例化时,我们才必须研究T::type是什么。但从我们实际需要的角度来看,这有点延迟了。

您需要的是强制traits本身的实例化失败,或者强制traits没有一个名为type的成员别名,如果T没有。这是一个简单的版本:

代码语言:javascript
复制
template <class T, class... Args>
struct traits;

template <class T>
struct traits<valid<T>> {
    using type = T;
};

但你会想要比这更结实的东西。

不幸的是,您不能添加尾随的默认模板参数,例如:

代码语言:javascript
复制
template <typename T, typename... Args, typename = typename T::type>
struct traits {
    using type = typename T::type;
};

由于有了[temp.param]/15,但是有了概念,您可以这样做:

代码语言:javascript
复制
template <typename T>
concept Typed = requires {
    typename T::type;
};

template <Typed T, typename... Args>
struct traits {
    using type = typename T::type;
};
票数 2
EN

Stack Overflow用户

发布于 2018-08-29 16:38:45

SFINAE要求替换失败是实例化的“即时上下文”。否则会出现严重错误。

如果没有中间traits类型,则function<invalid<int>, valid<int>, invalid<int>::type>的实例化将导致即时上下文中的一个错误,因为invalid<int>没有名为type的成员,因此SFINAE开始执行任务。

对于中间traits类型,错误发生在定义traits<invalid<int>>的实例化过程中,因为这需要不存在的invalid<int>::type。这并不是在直接的上下文中,所以会发生一个严重的错误。

要解决这个问题,您必须确保traits始终有一个有效的定义。这样做是可以的:

代码语言:javascript
复制
template <class T, class = void>
struct traits {};

template <class T>
struct traits<T, std::void_t<typename T::type>> {
    using type = typename T::type;
};
票数 3
EN

Stack Overflow用户

发布于 2018-08-29 16:38:22

如果你阅读了SFINAE的描述,就会有这样一句话:

只有函数类型或其模板参数类型或显式说明符(自C++20)的直接上下文中的类型和表达式中的故障是SFINAE错误。

traits<T, U>::type是在function的直接上下文中访问的,而不是sfinae的上下文。这就是它导致编译器错误的原因。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52082069

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档