【问题标题】:Generic wrapper behaving in the same way as wrapped type泛型包装器的行为方式与包装类型相同
【发布时间】:2021-10-25 06:37:07
【问题描述】:
#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&lt;int&gt; 不允许这样的函数编译:

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

【问题讨论】:

  • explicit 构造函数只能通过调用它来调用,例如return std::vector&lt;int&gt;(123);。在一般情况下,对于任意类和类型,此任务是不可能的。在某些情况下,可以通过继承接口或“Wrapper”应该是“reerence_wapper”。
  • 你的构造函数应该是有条件的explicit。但不完全确定是什么条件。
  • { 1, 2, 3 } 没有类型。所以不能从T&amp;&amp;推导出来(可以从initializer_list或者T(&amp;)[N]推导出来。

标签: c++ constructor wrapper


【解决方案1】:

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

至于一个类型是否可以从另一个类型隐式构造,我们可以结合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&amp;&amp;推导出来,所以还是需要显式指定其类型如std::initializer_list&lt;int&gt;{1, 2, 3}

Demo.

【讨论】:

  • 请注意,这个答案是 C++20(概念可以被 SFINAE 和 explicit(expression) 代替)。
  • 错字:explicit(!implicitly_constructible&lt;Wrapped, First&amp;&amp;, Rest&amp;&amp;...&gt;)(缺少Rest,并且应保留引用,因为采用非常量左值的构造函数无法使用U 构造)。
  • @Jarod42。我不太明白为什么我们需要参考,is_constructible/is_convertible 不是已经在使用std::declval&lt;Args&gt;()了吗?
  • 我的错误参考(由于折叠,所以First 将是U&amp; 对于我的问题案例,所以不再是问题)。 std::is_convertible 是错误的工具,因为它只处理一个参数。您可以像处理空箱一样执行implicitly_constructible。你可能有 explicit Foo(int); Foo(int, int); 并且你错误地认为第二个是 explicit (并且反向大小写也是有问题的)。
  • "你可能会像处理空的情况一样做implicitly_constructible。" 我一开始也是这么想的,直到我发现vector(initializer_list&lt;T&gt;)不是explicit将使[](const vector&lt;int&gt;&amp;){}({0}); 变得格式良好。 ;-(
【解决方案2】:

额外的 ctor 可能会有所帮助...

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

    template <class T, typename... Args>
    Wrapper(std::initializer_list<T> lst, Args&&... args)
    : wrapped(std::move(lst), std::forward<Args>(args)...)
    {
    }
private:
    Wrapped wrapped;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    相关资源
    最近更新 更多