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

标签 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 的“精简版概念”,我认为这确实是这个问题想要的)。

最佳答案

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

#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 , 和另一种 CA 隐式转换和 B , 但对于 T 的隐式转换是模棱两可的。您希望消除歧义,以便 C可以从 T 隐式转换使用转换序列 T => A => C , 但您必须在不更改 A 的定义的情况下这样做和 B .

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

我猜想您想要解决的更普遍的问题是制作 C任何类型明确隐式转换U可以隐式转换为 A使用转换序列 U => A => C .这可以通过向 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&) 产生歧义U 情况下的构造函数和无限递归是例如A&A&& .

值得注意的是,此解决方案不需要更改 T 的定义, A , B , C(A const&)C(B const&) , 所以它很好地独立。

关于c++ - 防止构造函数参数隐式转换为外部库类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21145035/

相关文章:

c++ - 为什么具有固定底层 char 类型的枚举的值解析为 fct(int) 而不是 fct(char)?

c++ - 左移的负数是否*总是*用 "1"而不是 "0"填充?

c++ - 二叉树中序遍历显示错误

c++ - 如何使用 boost::asio::ip::tcp::resolver 处理本地和公共(public) ip

C++ 模板 - 如何跨多个 MTU 使用

c++ - 处理文件名boost::filesystem::stem中带有多个“。”的文件

c++ - 使相同的 C++ 类型别名不兼容

c++ - 在头文件中使用 lambda 会违反 ODR 吗?

c++ - 使用 std::is_same 进行元编程

c++ - 如何在 OpenGL C++ 中加载 3D 模型?