【问题标题】:Prevent implicit conversion of constructor arguments to external library type防止将构造函数参数隐式转换为外部库类型
【发布时间】:2014-01-15 18:06:01
【问题描述】:

考虑以下代码:

#include <boost/range.hpp>
#include <boost/iterator/counting_iterator.hpp>

typedef boost::iterator_range<boost::counting_iterator<int>> int_range;

template <typename T>
class Ref {
    T* p_;    
  public:    
    Ref(T* p) : p_(p) { }
    /* possibly other implicit conversion constructors,
       but no unconstrained template constructors that don't
       use the explicit keyword... */  
    operator T*() const { return p_; }
    operator const T*() const { return p_; }    
};

struct Bar { };

class Foo {    
  public:    
    Foo(int a, char b) { /* ... */ }    
    Foo(int a, const Ref<Bar>& b) { /* ... */ }     
    Foo(int a, const int_range& r) { /* ... */ }     
};

int main() {
  Bar b;
  Foo f(5, &b);
  return 0;
}

此代码无法编译,因为Foo 构造函数的使用不明确,因为boost::iterator_range 显然有一个模板化构造函数,它接受单个参数并且未声明为explicit。假设更改 Ref 的结构不是一种选择,我该如何解决这个问题?我想出了以下可能的解决方案,但它很难看且不易维护,尤其是如果Foo 的构造函数不止几个:

template<typename range_like>
Foo(
  int a, 
  const range_like& r,
  typename std::enable_if<
    not std::is_convertible<range_like, Ref<Bar>>::value
      and std::is_convertible<range_like, int_range>::value,
    bool
  >::type unused = false
) { /* ... */ } 

或类似

template<typename range_like>
Foo(
  int a, 
  const range_like& r,
  typename std::enable_if<
    std::is_same<typename std::decay<range_like>::type, int_range>::value,
    bool
  >::type unused = false
) { /* ... */ } 

它的缺点是int_range 的所有其他隐式类型转换都被禁用,因此依赖于boost 的未指定功能(我的直觉告诉我这可能是个坏主意)。有一个更好的方法吗? (除了 C++14 “concepts-lite”,我认为这确实是这个问题想要的)。

【问题讨论】:

  • 添加一个Foo(int, Bar*) 构造函数,也许?
  • @IgorTandetnik 不错的想法,但这并不能解决我试图解决的更普遍的问题。与实际解决方案相比,示例的简单性更是一个问题。
  • 举个例子来说明这个你试图解决的更普遍的问题怎么样?
  • 报告要提升的错误?
  • @IgorTandetnik 这个例子运行良好。假设还有其他东西可以转换为Ref&lt;Bar&gt;。不是无限数量的东西,就像模板化的构造函数一样,但不仅仅是指向Bar 的指针。我将在Ref 类中添加一个/* ... */ 来表明这一点。

标签: c++ boost c++11 constructor implicit-conversion


【解决方案1】:

我认为这个程序是您问题的一个最小示例:

#include <iostream>

struct T {};

struct A {
  A(T) {}
};

struct B {
  B(T) {}
};

struct C {
  C(A const&) { std::cout << "C(A)\n"; }
  C(B const&) { std::cout << "C(B)\n"; }
};

int main() {
  C c{T{}};
}

您有两种类型AB 都可以从另一种类型T 隐式转换,另一种类型C 可以从AB 隐式转换,但从T 隐式转换是模棱两可的。您希望消除这种情况的歧义,以便使用转换序列 T =&gt; A =&gt; CT 隐式转换 C,但您必须在不更改 AB 的定义的情况下这样做。

显而易见的解决方案 - 在 cmets 中已经提出 - 是为 C 引入第三个转换构造函数:C(T value) : C(A(value)) {}。您拒绝了这个解决方案,因为它不够普遍,但没有澄清“一般”问题是什么。

我猜想您想要解决的更一般的问题是使C 明确地从任何 类型U 隐式转换为A 使用转换序列@987654341 @。这可以通过向C (Live code demo at Coliru) 引入额外的模板构造函数来实现:

template <typename U, typename=typename std::enable_if<
  !std::is_base_of<A,typename std::decay<U>::type>::value &&
   std::is_convertible<U&&, A>::value>::type>
C(U&& u) : C(A{std::forward<U>(u)}) {}

模板构造函数与C(U) 直接匹配,因此明显优于需要转换的C(A)C(B) 构造函数。它被限制为只接受 U 这样的类型

  • U 可转换为 A(原因很明显)
  • U 不是 A 或对A 的引用或从A 派生的类型,以避免与C(const A&amp;) 构造函数的歧义以及U 的情况下的无限递归例如A&amp;A&amp;&amp;

值得注意的是,此解决方案不需要更改 TABC(A const&amp;)C(B const&amp;) 的定义,因此它非常独立。

【讨论】:

  • 那么,如果我理解正确,您提出第二种解决方案? (您使用了is_base_of 而不是is_same 的重要修改)
  • @DavidHollman 不完全是。您的第二个解决方案仅接受 const int_range&amp; 类型,有效地禁用到 int_range 的隐式转换(正如您在 OP 中提到的那样)。我的解决方案添加了一个新的构造函数,与需要转换的现有构造函数相比,它更适合可转换为 A (Ref&lt;Bar&gt;) 的任何内容。 It would translate into your concrete example like this.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-11
  • 2011-09-18
  • 2020-12-13
  • 2019-05-14
  • 1970-01-01
相关资源
最近更新 更多