防止将构造函数参数隐式转换为外部库类型

Dav*_*man 6 c++ constructor boost implicit-conversion c++11

请考虑以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这段代码不能编译,因为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
) { /* ... */ } 
Run Code Online (Sandbox Code Playgroud)

或类似的

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
) { /* ... */ } 
Run Code Online (Sandbox Code Playgroud)

它的缺点是所有其他隐式类型转换都int_range被禁用,因此依赖于未指定的特征boost(而且我的直觉告诉我,这可能是一个坏主意).有一个更好的方法吗?(除了C++ 14"概念 - 精简版",这就是我认为这个问题的真正原因).

Cas*_*sey 8

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

#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{}};
}
Run Code Online (Sandbox Code Playgroud)

您有两种类型A,B它们都可以从另T一种类型C隐式转换,另一种类型可以从A和隐式转换B,但是隐式转换T是不明确的.你渴望消除歧义的情况,以便C为隐式转换T使用转换序列T => A => C,但你必须在不改变的定义,这样做AB.

显而易见的解决方案 - 已经在评论中提出 - 是为了引入第三个转换构造函数C:C(T value) : C(A(value)) {}.你已经拒绝了这个解决方案,因为它不够通用,但没有说明"一般"问题是什么.

我猜想你想要解决的更普遍的问题是C任何U可隐式转换为A使用转换序列的类型中明确地隐式转换U => A => C.这可以通过引入一个额外的模板构造函数来实现C(Coliru的Live代码演示):

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)}) {}
Run Code Online (Sandbox Code Playgroud)

模板的构造是一个直接匹配C(U),因此明确地优于C(A)C(B)这将需要一个转换构造.它仅限于接受U这样的类型

  • U是可转换的A(出于显而易见的原因)
  • U为了避免与构造函数的歧义和在例如或的情况下的无限递归,它不是 A或者是对它的引用A或类型.AC(const A&)UA&A&&

值得注意的是该解决方案不需要改变的定义T,A,B,C(A const&)或者C(B const&),因此它是很好的自足.