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

Wide variety of unambiguous constructors for the same class

本文关键字:构造函数 一类      更新时间:2023-10-16

我有以下类:

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 中),同时保持代码尽可能简单,都是非常受欢迎的。

谢谢。

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

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

由于此构造函数至少接受 1 个参数,因此默认构造函数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的迭代器类型显式禁用此构造函数。

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

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());

假设类定义如下:

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);

我们将填写beginendm_data

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

此 c'tor 将允许以下调用:

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_data。我们使用std::beginstd::end对其进行迭代,因为它们比它们的.begin().end()对应项更通用,并且支持更多类型,例如原始数组。 此 c'tor 将允许以下调用:

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);

从初始值设定项列表构造

template <class X>
Foo (std::initializer_list<X> && list)
: m_data(std::begin(list), std::end(list));

注意:我们通常将列表作为 Rvalue 引用,但我们也可以添加一个Foo (const std::initializer_list<X> & list)来支持 Lvalue 的构造。

我们通过再次迭代列表来填充我们的m_data。这个c'tor将支持:

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

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

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

在这里,将数据填充到容器中有点棘手。我们使用参数扩展来解包和推送每个参数。这个 c'tor 允许我们调用:

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

全班

最终的结果相当不错:

template <class T>
class Foo
{
public:
Foo () {
std::cout << "Default" << std::endl;
}
template <class ... X>
Foo (X ... args) {
int dummy[sizeof...(args)] = { (m_data.push_back(args), 0)... };
static_cast<void>(dummy);
std::cout << "VA-args" << std::endl;
}
template <class X>
Foo (std::initializer_list<X> && list)
: m_data(std::begin(list), std::end(list)) {
std::cout << "Initializer" << std::endl;
}
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)) {
std::cout << "Container" << std::endl;
}
template <class Iterator>
Foo (Iterator first, Iterator last, typename Iterator::iterator_category * = 0)
: m_data(first, last) {
std::cout << "Iterators" << std::endl;
}
private:
std::vector<T> m_data;
};