推荐的模拟概念和约束的方法是什么?

时间:2018-12-25 13:09:20

标签: c++ c++11 template-meta-programming sfinae enable-if

在引入概念和约束之前,有几种方法可以模拟此编译时检查。以“ order()”函数为例:(如何在没有概念或约束的情况下实现LessThanComparable是另一个故事)

  • 使用static_assert

    template <typename T, typename U>
    void order(T& a, U& b)
    {
        static_assert(LessThanComparable<U,T>, "oh this is not epic");
        if (b < a)
        {
            using std::swap;
            swap(a, b);
        }
    }
    

    此方法不适用于函数重载。

  • 使用typename = enable_if

    template <typename T, typename U,
        typename = std::enable_if_t<LessThanComparable<U,T>>>>
    void order(T& a, U& b)
    {
        if (b < a)
        {
            using std::swap;
            swap(a, b);
        }
    }
    

    如果“聪明”的家伙用手指定了第三个参数怎么办?

  • 在函数原型中使用enable_if

    template <typename T, typename U>
    std::enable_if_t<LessThanComparable<U,T>>, void> order(T& a, U& b)
    {
        if (b < a)
        {
            using std::swap;
            swap(a, b);
        }
    }
    

    有时在函数重载中也不起作用。

  • 使用enable_if作为虚拟非类型模板参数的类型

    template <typename T, typename U,
        std::enable_if_t<LessThanComparable<U,T>>, void*> = nullptr> // or int = 0
    void order(T& a, U& b)
    {
        if (b < a)
        {
            using std::swap;
            swap(a, b);
        }
    }
    

    我以前看过这个,我想不到任何缺点。

  • 和许多其他变体。

哪个是更可取或推荐的?优点和缺点是什么? 任何帮助表示赞赏。

3 个答案:

答案 0 :(得分:5)

这是一个复杂的主题,要回答您的问题并不容易。

无论如何,有些观察/建议,却没有任何详尽的伪装。

(1)static_assert()方式

static_assert(LessThanComparable<U,T>, "oh this is not epic");
如果希望使用仅适用于某些类型的函数并且如果使用错误的类型进行调用会给出错误(明确的错误,因为您可以选择错误消息),则

是一个很好的解决方案。

但是,当您需要替代方案时,通常是错误的解决方案。不是SFINAE解决方案。因此,使用错误类型的参数调用该函数会产生错误,并且不允许在替换中使用另一个函数。

(2)您对

是正确的
typename = std::enable_if_t</* some test */>>

解决方案。用户可以手动显示第三个参数。开玩笑,我说这个解决方案可以被“劫持”。

但这不是此解决方案的唯一缺点。

假设您有两个互补的foo()函数,这些函数必须通过SFINAE启用/禁用;当测试为真时,第一个,如果测试为假,则为第二个。

您可以认为以下解决方案很危险(可以被劫持)但可以起作用

/* true case */
template <typename T, typename = std::enable_if_t<true == some_test_v<T>>>
void foo (T t)
 { /* something with t */ }

/* false case */
template <typename T, typename = std::enable_if_t<false == some_test_v<T>>>
void foo (T t)
 { /* something with t */ }

错误:此解决方案根本不起作用,因为您不是在启用/禁用第二种类型名,而是仅启用/禁用第二种类型名的默认值。因此,您并没有完全启用/禁用这些功能,并且编译器必须考虑具有相同签名的两个功能(一个功能的签名不取决于默认值)。这样您就会发生碰撞并获得错误。

以下解决方案,对返回的类型进行SFINAE

std::enable_if_t<LessThanComparable<U,T>, void> order(T& a, U& b)

(也没有void,这是默认类型

std::enable_if_t<LessThanComparable<U,T>> order(T& a, U& b)

)或第二种类型(遵循Yakk关于允许使用非标准void *的建议)

template <typename T, typename U,
          std::enable_if_t<LessThanComparable<U,T>>, bool> = true> 

是(IMHO)很好的解决方案,因为它们都避免了劫持风险,并且与具有相同名称和签名的两个互补功能兼容。

我建议第三种可能的解决方案(不可劫持,互补兼容),即添加第三种具有SFINAE启用/禁用类型的默认值:

template <typename T, typename U>
void order(T& a, U& b, std::enable_if_t<LessThanComparable<U,T>> * = nullptr)

另一种可能的解决方案是完全避免SFINAE,而是使用标记分派;像

template <typename T, typename U>
void order_helper (T & a, U & b, std::true_type const &)
 { if (b < a) { std::swap(a, b); } }

// something different if LessThanComparable is false ?
template <typename T, typename U>
void order_helper (T & a, U & b, std::false_type const &)
 { /* ???? */ }

template <typename T, typename U>
void order (T & a, U & b)
 { order_helper(a, b, LessThanComparable<U,T>{}); }

如果条件为true,则LessThanComplarable将从std::true_type继承,条件为false则从std::false_type继承。

否则,如果LessThanComparable仅提供布尔值,则对order_helper()的调用可以是

order_helper(a, b, std::integral_constant<bool, LessThanComparable<U,T>>{});

(3)如果可以使用C ++ 17,则有一种if constexpr方式可以避免很多重载

template <typename T, typename U>
void order(T& a, U& b)
{
   if constexpr ( LessThanComparable<U, T> )
    { 
      if ( b < a )
         std::swap(a, b);
    }
   else
    {
      // what else ?
    }
}

答案 1 :(得分:2)

至少在某些版本的标准中,不允许使用类型void*的非类型模板参数;我会使用bool和值=true

否则,请使用它。

答案 2 :(得分:1)

您应该查看Array.forEach库如何模拟概念https://github.com/ericniebler/range-v3/blob/master/include/range/v3/range_concepts.hpp

还有一种使用模板别名来实现类似于概念Using alias templates for sfinae: does the language allow it?

的方法

而且,您错过了列表中的range-v3个变体:

decltype