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

时间:2014-01-15 18:06:02

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

请考虑以下代码:

#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“概念 - 精简版”,这就是我想到的问题)。

1 个答案:

答案 0 :(得分:7)

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

#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可以隐式转换为A和{{1}但是,B的隐式转换是模糊的。您希望消除歧义情况,以便T可以使用转化序列CT隐式转换,但您必须这样做而不更改T => A => C和{的定义{1}}。

明显的解决方案 - 已在评论中提出 - 是为A引入第三个转换构造函数:B。你已经拒绝了这个解决方案,因为它不够通用,但没有说明“一般”问题是什么。

我猜想你想要解决的更普遍的问题是让C明确地从任何类型C(T value) : C(A(value)) {}隐式转换为可隐式转换为C的{​​{1}}转化顺序U。这可以通过向ALive code demo at Coliru)引入额外的模板构造函数来实现:

U => A => C

模板构造函数是C的直接匹配,因此明确优先于需要转换的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 not A或对U的引用或从A派生的类型,以避免与{{1}的歧义在A例如的情况下,构造函数和无限递归AC(const A&)

值得注意的是,此解决方案不需要更改UA&A&&TA的定义,因此它非常自包含