泛型包装的行为方式与包装类型相同

2022-04-18 00:00:00 constructor wrapper c++
#include <utility>
#include <vector>

template <typename Wrapped>
class Wrapper
{
public:
    template <typename... Args>
    Wrapper(Args&&... args)
    : wrapped(std::forward<Args>(args)...)
    {
    }

private:
    Wrapped wrapped;
};

Wrapper<std::vector<int>> intended()
{
    std::vector<int>::allocator_type allocator;

    return { { 1, 2, 3 }, allocator }; // doesn't compile
}

Wrapper<std::vector<int>> unintended()
{
    return 123; // calls 'explicit vector(size_type count)'
}
应该做些什么才能使这样的Wrapper几乎不可见?例如,返回std::vector<int>不允许编译这样的函数:

std::vector<int> get_vector()
{
    return 123; // doesn't compile
}

解决方案

对于T的默认构造函数,可以通过检查const T&是否可以用{}初始化来判断它是否隐式。

对于一个类型是否可以从另一个类型隐式构造,我们可以结合std::is_constructiblestd::is_convertible来检查。

因此您的Wrapper的构造函数可以根据上面的规则有条件地explicit,例如:

#include <utility>

template<typename T, typename... Args>
concept implicitly_constructible = 
  requires { [](const T&) {}({std::declval<Args>()...}); };

template<typename Wrapped>
class Wrapper {
 public:
  template<typename T>
    requires std::is_constructible_v<Wrapped, T>
  explicit(!std::is_convertible_v<T, Wrapped>)
  Wrapper(T&& t)
  : wrapped(std::forward<T>(t)) { }

  template<typename... Args>
    requires (sizeof...(Args) != 1) && 
             std::is_constructible_v<Wrapped, Args...>
  explicit(!implicitly_constructible<Wrapped, Args...>)
  Wrapper(Args&&... args)
  : wrapped(std::forward<Args>(args)...) { }
 private:
  Wrapped wrapped;
};

需要注意的是,{1, 2, 3}不能从Args&&推导出来,所以您还需要明确指定它的类型,比如std::initializer_list<int>{1, 2, 3}

Demo.

相关文章