我有以下课程:
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中)的任何建议,同时也保持代码尽可能简单,这是非常受欢迎的。
谢谢。
答案 0 :(得分:3)
实现f1
的最简单方法 - f4
(似乎采用不是容器或迭代器的已知类型T
的可变数量的参数)是这样的:< / p>
template<typename... Args>
Foo(T arg, Args... args) {...}
由于此构造函数至少使用一个参数,因此默认构造函数f0
不存在歧义。由于第一个参数是T
类型,因此以下构造函数不存在歧义。
如果您希望以不同于其他容器的方式处理std::vector
和std::list
,则可以创建部分专用的帮助程序模板,以检查参数是否是给定模板的实例:
template<typename>
struct is_vector : std::false_type {};
template<typename T, typename Allocator>
struct is_vector<std::vector<T, Allocator>> : std::true_type {};
并像这样使用它来实现f5
和f7
:
template<typename T, Constraint = typename std::enable_if<is_vector<typename std::remove_reference<T>::type>::value, void>::type>
Foo(T arg) {...}
通过测试std::vector
和std::list
各自的迭代器类型,您可以以相同的方式实现f6
和f8
。
您可以检查是否存在成员函数begin()
和end()
来实现f9
(我想),如下所示:
template<typename T>
Foo(T arg, decltype(arg.begin())* = 0, decltype(arg.end())* = 0) {...}
但是,您必须使用您创建的帮助程序模板为std::vector
和std::list
显式禁用此构造函数,以避免歧义。
要检查参数是否是实现f10
的某个迭代器,您可以使用std::iterator_traits
:
template<typename T, typename Constraint = typename std::iterator_traits<T>::iterator_category>
Foo(T begin, T end) {...}
同样,您必须为std::vector
和std::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)
接受支持begin
和end
迭代器的任何容器。
Foo(iterator begin, iterator end)
使用begin
和end
迭代器初始化类。
用法:
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_data
和begin
填写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_data
和std::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);