代码之家  ›  专栏  ›  技术社区  ›  Vincent

SFINAE未能与中介类型特征一起工作

  •  1
  • Vincent  · 技术社区  · 6 年前

    考虑以下测试代码:

    // 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 ●类型
    • 建筑物 traits ●类型 作为 T::type
    • 超载的 function 只有当第一个参数的类型 traits<T>::type 已定义
    • 超载的 sfinae 应该能够调用 即使第一个论点是 无效

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

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

    然后按预期工作并输出:

    function(T)
    sfinae(Args&&...)
    function(T)
    sfinae(const invalid<T>&, Args&&...)
    

    问题:

    3 回复  |  直到 6 年前
        1
  •  2
  •   Barry    6 年前

    从根本上说,这可以归结为“直接上下文”在 [temp.deduct]/8 ,sfinae规则,它没有非常明确的定义(参见 cwg 1844 ):

    [注: 如果不需要诊断,程序仍然是错误的。访问检查作为替换过程的一部分进行。 只有函数类型、其模板参数类型及其显式说明符的直接上下文中的无效类型和表达式才能导致演绎失败。 [注: 对类型和表达式的替换可能导致诸如类模板专门化和/或函数模板专门化的实例化、隐式定义函数的生成等效果。此类效果不在直接上下文中,并且可能导致程序格式错误。 [尾注]

    在这种情况下,可以说直接的背景就是看到这一点 traits<T,U>::type T::type 是。但这比我们实际需要的要晚一点。

    traits 使自己失败或被迫 没有名为的成员别名 type 如果 T

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

    但你会想要比这更强壮的东西。


    template <typename T, typename... Args, typename = typename T::type>
    struct traits {
        using type = typename T::type;
    };
    

    由于 [temp.param]/15

    template <typename T>
    concept Typed = requires {
        typename T::type;
    };
    
    template <Typed T, typename... Args>
    struct traits {
        using type = typename T::type;
    };
    
        2
  •  3
  •   Brian Bi    6 年前

    SFINAE要求替换失败必须是实例化的“直接上下文”。否则将发生硬错误。

    traits 类型的实例化 function<invalid<int>, valid<int>, invalid<int>::type> 在直接上下文中导致错误,因为 invalid<int> type

    中间产物 特点 类型,则错误发生在定义的实例化过程中 traits<invalid<int>> 因为这需要不存在的 invalid<int>::type . 这不在直接上下文中,因此会发生硬错误。

    特点 总是有一个有效的定义。可以这样做:

    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
  •  1
  •   Maxim Egorushkin    6 年前

    如果你读过 SFINAE

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

    那个 traits<T, U>::type 在中访问 属于 function 而不是你的 sfinae . 这就是它导致编译器错误的原因。