适用于同一类的各种明确的构造函数

时间:2017-10-21 09:54:15

标签: c++

我有以下课程:

class Foo
{
public:
    // Constructors here
private:
    std::vector<X> m_data; // X can be any (unsigned) integer type
};

我想要以下代码:

Foo f0;
Foo f1(1); // and/or f1({1})
Foo f2(1, 2); // and/or f2({1, 2})
Foo f3(1, 2, 3); // and/or f3({1, 2, 3})
Foo f4(1, 2, 3, 4); // ... and so on
std::vector<int> vec = {1, 2, 3, 4};
Foo f5(vec);
Foo f6(vec.begin(), vec.end());
std::list<std::size_t> list = {1, 2, 3, 4};
Foo f7(list);
Foo f8(list.begin(), list.end());
std::any_iterable_container container = {1, 2, 3, 4};
Foo f9(container);
Foo f10(container.begin(), container.end());
// PS: I guess I only want containers/iterators that store individual
// values and not pairs (e.g., I don't want care about std::map
// because it does not make sense anyway). 

到目前为止,我已经尝试将SFINAE,构造函数重载与所有可能的类型,可变参数模板等结合起来。每当我修复一个构造函数的情况时,其他的都会崩溃。此外,我写的代码变得非常复杂,难以阅读。然而,问题似乎很简单,我想我只是以错误的方式接近它。关于如何编写构造函数(理想情况下在C ++ 17中)的任何建议,同时也保持代码尽可能简单,这是非常受欢迎的。

谢谢。

3 个答案:

答案 0 :(得分:3)

实现f1的最简单方法 - f4(似乎采用不是容器或迭代器的已知类型T的可变数量的参数)是这样的:< / p>

template<typename... Args>
Foo(T arg, Args... args) {...}

由于此构造函数至少使用一个参数,因此默认构造函数f0不存在歧义。由于第一个参数是T类型,因此以下构造函数不存在歧义。

如果您希望以不同于其他容器的方式处理std::vectorstd::list,则可以创建部分专用的帮助程序模板,以检查参数是否是给定模板的实例:

template<typename>
struct is_vector  : std::false_type {};

template<typename T, typename Allocator>
struct is_vector<std::vector<T, Allocator>>  : std::true_type {};

并像这样使用它来实现f5f7

template<typename T, Constraint = typename std::enable_if<is_vector<typename std::remove_reference<T>::type>::value, void>::type>
Foo(T arg) {...}

通过测试std::vectorstd::list各自的迭代器类型,您可以以相同的方式实现f6f8

您可以检查是否存在成员函数begin()end()来实现f9(我想),如下所示:

template<typename T>
Foo(T arg, decltype(arg.begin())* = 0, decltype(arg.end())* = 0)  {...}

但是,您必须使用您创建的帮助程序模板为std::vectorstd::list显式禁用此构造函数,以避免歧义。

要检查参数是否是实现f10的某个迭代器,您可以使用std::iterator_traits

template<typename T, typename Constraint = typename std::iterator_traits<T>::iterator_category>
Foo(T begin, T end)  {...}

同样,您必须为std::vectorstd::list的迭代器类型显式禁用此构造函数。

答案 1 :(得分:2)

这个想法是定义这样的类:

template <typename X>
class Foo
{
public:
  Foo() { };

  Foo(initializer_list<int> l) :m_data(l) { };

  template<typename container>
  Foo(container const & c) :m_data(c.begin(), c.end()) {};

  template<typename iterator>
  Foo(iterator begin, iterator end) :m_data(begin, end) { };
private:
  std::vector<X> m_data;
};

其中:

Foo()是默认(非参数)构造函数。

Foo(initializer_list<int> l)接受{1, 2, 3}等列表。

Foo(container const & c)接受支持beginend迭代器的任何容器。

Foo(iterator begin, iterator end)使用beginend迭代器初始化类。

用法:

  Foo<int> f0;
  Foo<int> f1({1});
  Foo<int> f2({1, 2});
  Foo<int> f3({1, 2, 3});
  Foo<int> f4({1, 2, 3, 4});
  std::vector<int> vec = {1, 2, 3, 4};
  Foo<int> f5(vec);
  Foo<int> f6(vec.begin(), vec.end());
  std::list<size_t> list = {1, 2, 3, 4};
  Foo<size_t> f7(list);
  Foo<size_t> f8(list.begin(), list.end());
  set<unsigned> container = {1, 2, 3, 4};
  Foo<unsigned> f9(container);
  Foo<unsigned> f10(container.begin(), container.end());

答案 2 :(得分:1)

假设该类定义如下:

template <class T>
class Foo
{
public:
    [..]

private:
    std::vector<T> m_data;
}

让我们将这项任务分解为子任务:

从迭代器构造

template <class Iterator>
Foo (Iterator begin, Iterator end, typename Iterator::iterator_category * = 0)
    : m_data(begin, end);

我们将从m_databegin填写end

第三个参数将确保只声明Iterator的{​​{1}}类型与此原型匹配。由于此参数的默认值为iterator_category且从未指定,因此仅在模板推导过程中才有用。当编译器检查这是否是正确的原型时,如果类型0不存在,它将跳过它。由于Iterator::iterator_category是每个标准迭代器的必备类型,因此它适用于它们。

此c'tor将允许以下呼叫:

iterator_category

从容器构建

std::vector<int> vec = {1, 2, 3, 4};
Foo<int> f(vec.begin(), vec.end());
-- AND --
std::list<std::size_t> list = {1, 2, 3, 4};
Foo<int> f(list.begin(), list.end());

我们将从给定的容器填充template <class Container> Foo (const Container & container, decltype(std::begin(container))* = 0, decltype(std::end(container))* = 0) : m_data(std::begin(container), std::end(container)); 。我们使用m_datastd::begin对其进行迭代,因为它们比std::end.begin()对应项更通用,并且支持更多类型,例如原始数组。 这个c'tor将允许以下呼叫:

.end()

从初始化列表构建

std::vector<int> vec = {1, 2, 3, 4};
Foo<int> f(vec);
-- AND --
std::list<std::size_t> list = {1, 2, 3, 4};
Foo<int> f(list);
-- AND --
std::array<int,4> arr = {1, 2, 3, 4};
Foo<int> f(arr);
-- AND --
int arr[] = {1, 2, 3, 4};
Foo<int> f(arr);

注意:我们将列表作为Rvalue引用,因为它通常是这种情况,但我们也可以添加template <class X> Foo (std::initializer_list<X> && list) : m_data(std::begin(list), std::end(list)); 来支持Lvalues的构造。

我们通过再次遍历列表来填写我们的Foo (const std::initializer_list<X> & list)。这个c'tor将支持:

m_data

来自可变数量参数的构造函数

Foo<int> f1({1});
Foo<int> f2({1, 2});
Foo<int> f3({1, 2, 3});
Foo<int> f4({1, 2, 3, 4});

这里,将数据填入容器有点棘手。我们使用参数扩展来解压缩并推送每个参数。这个c'tor允许我们打电话:

template <class ... X>
Foo (X ... args) {
    int dummy[sizeof...(args)] = { (m_data.push_back(args), 0)... };
    static_cast<void>(dummy);
}

全班

最终结果非常好:

Foo<int> f1(1);
Foo<int> f2(1, 2);
Foo<int> f3(1, 2, 3);
Foo<int> f4(1, 2, 3, 4);