c++ - 解决 C++ 构造函数和调用歧义

标签 c++ arrays templates ambiguity

我有一个漂亮的小万能数组类型,可以满足我到目前为止的所有需求。

template <typename T>
class Array
{
    ...

public:
    int Data; // custom value
    virtual void InitData() { Data = 0; }

    Array(const Array& array);
    template <typename U, typename = std::enable_if<std::is_same<U, T>::value, U>> Array(const Array<U>& array);
    template <typename... Ts> Array(const Ts&... items);

    void Add(const T& item);
    template <typename... Ts> void Add(const T& item, const Ts&... rest);
    void Add(const Array& array);
}

template <typename... Ts> Array(const Ts&... items); 让我可以执行 Array<T> array = { ... } 操作,并分配并返回 {...} 初始化列表。因为没有一个构造函数是显式的,所以它非常方便,但这也是我现在陷入困境的原因。

我希望能够向数组添加任何“合理”的内容。我现在的主要用例是:

using Curve = Array<float2>;

class Poly   : public Array<float2> { using Array::Array; void InitData() override { Data = 1; } };
class Curve2 : public Array<float2> { using Array::Array; void InitData() override { Data = 2; } };
class Curve3 : public Array<float2> { using Array::Array; void InitData() override { Data = 3; } };

上面的 std::is_same<> 内容专门是为了能够将所有曲线视为相同但不相同:不同程度的曲线类型,并且所有内容都是很好的“静态类型”,所以我在像 DrawCurve(const Curve&) 这样的函数中所做的一切是检查程度然后采取适当的行动。 Curve 是 Array 的一个很好的别名,Curve2 等是程度特化。它工作得非常好。

当我进行曲线构造时,我通常有一个曲线对象,我向其中添加点或曲线段。所以我希望能够做到:

Curve3 curve;
curve.Add(float2()); // ambiguity
curve.Add(Array<float2>());

不幸的是,当我调用 add 时,我在这里遇到了歧义,因为 Add() 将采用 float2Array<float2> ,这工作正常,但 Array 有隐式构造函数 template <typename... Ts> Array(const Ts&...) ,它可以采用 float2 作为参数。所以歧义在于

Array::Add(float2()); // and
Array::Add(Array<float2>(float2()));

我尝试过制作显式接受数组的构造函数,例如

template <typename A, typename = std::enable_if<std::is_same<A, Array>::value, A>>
void Add(const Array& array);

但是后来我收到了从 Curve3 到 float2 等的新转换错误,结果变得一团糟。

我希望模板或其他 C++ 好东西的深处有一个简单的解决方案,这正是我所需要的。 (是的,我知道我可以重命名方法::AddItem() 和::AddArray() ,问题将在一秒钟内结束,但我不想要这个,因为最终我想用 += 将所有这些加倍然后大多数情况下就使用它。

有什么想法吗?

最佳答案

观察你想要的

 template <typename... Ts> Array(const Ts&... items);

仅当参数包至少包含一项且该项的类型不是 Array 模板实例时才使用。如果参数包为空,则这将成为默认构造函数,因此我们单独处理这种情况。如果需要的话,继续显式定义一个默认构造函数,并让它做它需要做的事情;现在我们可以从这个用例中消除这种可能性,并继续前进。

解决了这一问题后,您在这里要做的就是仅当该构造函数有一个参数时才使用它:

 template <typename T1, typename... Ts> Array(const T1 &t1, const Ts&... items);

除了它使用的现有参数包之外,您还必须修改此构造函数才能使用显式 t1。这应该足够简单,但这还不够。还是有歧义。仅当 T1 不是Array 时,您才希望选择此构造函数。

可能有一种方法可以提出一些复杂的东西并将其填充到单个 std::enable_if 中,然后将其插入此模板中。但为了清楚和简单起见,我将使用一个辅助类:

template<typename T> class is_not_array : public std::true_type {};

template<typename T>
class is_not_array<Array<T>> : public std::false_type {};

然后添加一个简单的 std::enable_if 到这个构造函数的模板中,以便仅当它的第一个模板参数不是 Array 时才使用 SFINAE 选择这个构造函数,类似于您如何在另一个构造函数中使用 std::enable_if

这应该可以解决所有歧义。然后,在这个辅助类的帮助下,应该仅使用至少一个不是 Array 的模板参数来选择该构造函数。当它是Array时,此构造函数将不可解析,并且这种情况将转到另一个构造函数。

我还建议在模板中使用通用引用,而不是 const T &s。

关于c++ - 解决 C++ 构造函数和调用歧义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41762099/

相关文章:

c++ - doxygen 一次注释多个变量

c++ - 在c++中使用getline函数连续读取两行

c++ - 在结构体中使用 union 的目的是什么?

c++ - C++11 lambda 表达式背后的动机是什么?

javascript - 更改从 JSON 数组创建的列表中的项目

arrays - 遍历对象数组。如果找到正确的方法,则访问对象方法。否则在数组中创建一个新对象

c++ 以 'enter' 命中结束循环

c++ - 类型名称的概念/SFINAE 错误

c++ - 使用模板时,constexpr 函数不是 constexpr

c++ - map 上的无效模板参数 std::map< std::string, Stock*> &stocks