【问题标题】:SFINAE for types that don't have particular members没有特定成员的类型的 SFINAE
【发布时间】:2021-06-09 05:53:42
【问题描述】:

为什么以下代码无法编译?为具有特定成员的类型启用模板最简洁的解决方案是什么?如果模板变量被直接用于初始化它的表达式替换,它也会编译。

#include <iostream>

template<class T>
constexpr bool is_rect = std::is_same_v<decltype(T::left, T::top, T::right, T::bottom, void()), void>;

// compiles if is_rect<T> is replaced with the expression directly, but of course it's not a solution
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
    std::cout << "rect enabled\n";
}

template<class T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
void f(T)
{
    std::cout << "arithmetic enabled\n";
}

struct ActualType { float left, top, right, bottom; };

int main()
{   
    f(ActualType{});
    f(int{});
    return 0;
}

【问题讨论】:

  • 请在问题中包含编译器错误

标签: c++ templates sfinae


【解决方案1】:

问题是is_rect&lt;T&gt; 总是被指定为std::enable_if_t 的模板参数,作为f() 签名的一部分,当T 没有特定成员时,它是无效的表达式。

您可以应用部分特化来使is_rect 给出truefalse 基于类型是否具有特定成员。例如

template<class T, class = void>
constexpr bool is_rect = false;
template<class T>
constexpr bool is_rect<T, std::void_t<decltype(T::left, T::top, T::right, T::bottom)>> = true;

然后

template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
    std::cout << "rect enabled\n";
}

LIVE

【讨论】:

    【解决方案2】:

    您的is_rect&lt;T&gt; 的问题在于,如果T 缺少任何数据成员,则定义是格式错误的表达式,而不是计算结果为假的格式正确的表达式。

    您可以使用is_detected 习惯用法来测试您的模板是否格式正确:

    在 godbolt.org 上试用:Demo

    #include <type_traits>
    
    namespace detail
    {
    template<class AlwaysVoid, template <class...> class Op, class... Args>
    struct detector : std::false_type {};
    
    template<template <class...> class Op, class... Args>
    struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
    } // namespace detail
    
    template<template <class...> class Op, class... Args>
    constexpr bool is_detected_v = detail::detector<void, Op, Args...>::value;
    
    namespace detail
    {
    template<class T>
    using rect_detector = decltype(T::left, T::top, T::right, T::bottom);
    } // namespace detail
    
    template<class T>
    constexpr bool is_rect = is_detected_v<detail::rect_detector, T>;
    

    在 C++20 中,concepts 是为具有特定成员的类型启用模板的最简洁方式:

    在 godbolt.org 上试用:Demo

    #include <iostream>
    #include <type_traits>
    
    template<class T>
    concept arithmetic = std::is_arithmetic_v<T>;
    
    template<class T>
    concept rect = requires (T t)
    {
        { t.left };
        { t.top };
        { t.right };
        { t.bottom };
    };
    
    template<rect T>
    void f(T)
    {
        std::cout << "rect enabled\n";
    }
    
    template<arithmetic T>
    void f(T)
    {
        std::cout << "arithmetic enabled\n";
    }
    

    【讨论】:

    • 但是为什么直接放入模板参数列表时这个精确的表达式会起作用?
    • @oleksijp 因为此时它不再执行替换,即 SFINAE 中的 S。它只是选择格式良好的模板。而使用is_rect&lt;T&gt; 替换,则替换失败,导致编译器错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-14
    • 2019-10-10
    • 1970-01-01
    相关资源
    最近更新 更多