【问题标题】:What is the recommended way to simulate concepts and constraints? [closed]模拟概念和约束的推荐方法是什么? [关闭]
【发布时间】:2019-05-24 03:54:05
【问题描述】:

在引入概念和约束之前,有几种方法可以模拟这种编译时检查。以“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);
        }
    }
    

    我以前看过这个,我想不出有什么缺点。

  • 还有许多其他变体。

哪些更受欢迎或推荐?有什么好处和坏处? 任何帮助表示赞赏。

【问题讨论】:

  • 通常的方式是使用type traits
  • @πάνταῥεῖ 你的意思是实现LessThanComparable的常用方法?
  • 不,我的意思是一般。类型特征用于验证模板参数的概念和约束。
  • @πάνταῥεῖ 是的,它们在元编程中非常有用????
  • 非类型模板参数方法有一个小缺点。如果您希望将定义与声明分开(例如,对于模板成员函数),您必须复制“约束”。

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


【解决方案1】:

这是一个复杂的话题,不容易回答你的问题。

无论如何,一些观察/建议,并没有任何详尽无遗的借口。

(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 over return type

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> 

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

我建议第三种可能的解决方案(不可劫持,互补兼容),即添加第三个启用/禁用 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>{}); }

如果LessThanComplarable 在条件为真时从std::true_type 继承,在条件为假时从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 ?
    }
}

【讨论】:

    【解决方案2】:

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

    否则,使用它。

    【讨论】:

    • 谢谢,不知道你不能用void*作为非类型模板参数。
    • 无论如何,this page 似乎暗示在从 C++11 到 C++20 之前的每个标准中,都允许指向对象或函数的指针作为非类型模板参数。 void* 是否被视为“指向对象的指针”?
    • @l.f.我会在这里问一个语言律师标记的问题,或者阅读标准;我担心的是我遇到过其他合理的编译器拒绝 void ptr 参数,这很容易避免,所以我只是避免它们。
    【解决方案3】:

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

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

    而且,您错过了列表中的decltype 变体:

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

    【讨论】:

    • decltype 主要是编写特征的方式。以类似的方式std::void_t 将允许 SFINAE 类型而不是布尔值,但不会真正改变 OP 的变体。
    • 我认为decltype 更好地属于像LessThanComparable 这样的概念模拟器的实现。不过,它也是一个变体。
    猜你喜欢
    • 2013-03-18
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 2016-02-05
    相关资源
    最近更新 更多