首页 > 解决方案 > 复合赋值运算符的 C++ 概念

问题描述

我有一个普通二元运算符的概念

template<typename Op, typename T> concept is_binary_operation =
    requires (const T& t1, const T& t2) // e.g. a+b
{
    {Op()(t1,t2)}->std::convertible_to<T>;
};

以及复合赋值运算符的概念

template<typename Op, typename T> concept is_operation_and_assign =
   requires (T& t1, const T& t2) // e.g a += b;
{
  {Op()(t1,t2)}->std::convertible_to<T>;
};

对于复合赋值运算符,这按预期工作:

template<typename T> struct op_and_assign
{
    T& operator()(T& t1, const T& t2)
    {
        t1 += t2;
        return t1;
    }
};

这个“is_operation_and_assign”而不是“is_binary_operation”

std::cout << is_binary_operation<op_and_assign<double>, double> << " ";
std::cout << is_operation_and_assign<op_and_assign<double>, double> << std::endl;

打印“0 1”。然而,std::plus 满足这两个概念:

std::cout << is_binary_operation<std::plus<double>, double> << " ";
std::cout << is_operation_and_assign<std::plus<double>, double> << std::endl;

打印“1 1”。

我该如何改变“is_operation_and_assign”的概念,以便得到输出“1 0”,即它可以由 op_and_assign 而不是 std::plus 来实现?

为了更清楚我需要什么:我有两个版本的算法,一个使用复合赋值运算符,一个使用二元运算符:

template<typename Op, typename T>
int f() requires is_operation_and_assign<Op, T>
{   
    return 0;
}
template<typename Op, typename T>
int f() requires is_binary_operation<Op, T>
{ 
    return 1;
}

我可以调用 op_and_assign 的版本

f<op_and_assign<double>, double>();

但 std::plus 的版本

f<std::plus<double>, double>();

不编译。(错误:对“f”的调用不明确)

更新:同时我找到了一个解决方法:

当我简单地添加&& !is_binary_operation<Op, T>到第一个时f

template<typename Op, typename T>
int f() requires (is_operation_and_assign<Op, T>
               && !is_binary_operation<Op, T>)
{ 
    return 0;
}
template<typename Op, typename T>
int f()  requires is_binary_operation<Op, T> 
{ 
   return 1;
}

那么第二个电话不再模棱两可,即两者

f<op_and_assign<double>, double>();
f<std::plus<double>, double>();

编译(并选择所需的功能)。

标签: c++c++20c++-concepts

解决方案


澄清你的概念实际上在检查什么很重要,因为它不是你想象的那样。

这个:

template<typename Op, typename T> concept is_operation_and_assign =
   requires (T& t1, const T& t2) // e.g a += b;
{
  {Op()(t1,t2)}->std::convertible_to<T>;
};

检查您是否可以Op()(t1, t2)使用 aT&和 a调用,T const&并且您得到满足convertible_to<T>. 当您提供:

template<typename T> struct op_and_assign
{
    T& operator()(T& t1, const T& t2)
    {
        t1 += t2;
        return t1;
    }
};

作为第一个模板参数,它实际上检查了什么?这是一个未计算的表达式,我们正在检查是否可以调用op_and_assign<T>(). 我们没有评估呼叫操作员的主体,我们只是检查它是否是一个有效的呼叫。所以这与我们写的没有什么不同:

template<typename T> struct op_and_assign
{
    T& operator()(T& t1, const T& t2);
};

它没有被评估,没有实体,所以唯一重要的是约束。在这里,没有约束,所以op_and_assign只要参数是可转换的,它总是可以调用的。

当你这样做时:

is_binary_operation<op_and_assign<double>, double>

您实际上是在询问是否可以适当地转换参数。对于is_binary_operation,您提供了两个类型的参数double const&(来自您的 requires 表达式)但op_and_assign<double>需要一个double&. 这就是为什么此特定检查不起作用的原因。


对于如何修复它。op_and_assign这应该看起来像这样:

struct op_and_assign
{
    template <typename T, typename U>
    auto operator()(T&& t, U&& u) const -> decltype(t += u);
};

现在我们实际上正在检查我们是否可以执行+=.

但这不会改变您无法分配给double const&. 即使您没有进行您打算进行的检查,您也会在那里得到正确的答案。


推荐阅读