为每个可变模板参数生成一个 class 成员
Generating one class member per variadic template argument
我有一个模板 class,其中每个模板参数代表内部计算可以处理的一种类型的值。需要模板(而不是函数重载),因为值作为 boost::any 传递,并且它们的类型在运行前不明确。
为了正确转换为正确的类型,我希望每个可变参数类型都有一个成员列表,如下所示:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::vector<T1> m_argumentsOfType1;
std::vector<T2> m_argumentsOfType2; // ...
};
或者,我想将模板参数类型存储在一个列表中,以便用它做一些 RTTI 魔术 (?)。但是我也不清楚如何将它们保存在 std::initializer_list 成员中。
感谢您的帮助!
如 πìντα-ῥεῖ 的评论中所述,执行此类操作的一种方法是使用元组。他没有解释(可能是为了让你远离自己)的是它的样子。
这是一个例子:
using namespace std;
// define the abomination
template<typename...Types>
struct thing
{
thing(std::vector<Types>... args)
: _x { std::move(args)... }
{}
void print()
{
do_print_vectors(std::index_sequence_for<Types...>());
}
private:
template<std::size_t... Is>
void do_print_vectors(std::index_sequence<Is...>)
{
using swallow = int[];
(void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
}
template<class Vector>
void print_one(const Vector& v)
{
copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
cout << endl;
}
private:
tuple<std::vector<Types>...> _x;
};
// test it
BOOST_AUTO_TEST_CASE(play_tuples)
{
thing<int, double, string> t {
{ 1, 2, 3, },
{ 1.1, 2.2, 3.3 },
{ "one"s, "two"s, "three"s }
};
t.print();
}
预期输出:
1,2,3,
1.1,2.2,3.3,
one,two,three,
这里是使用 boost::variant
:
的不太有效的实现
template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;
template<typename ...Ts>
struct MyClass {
using var_vec = variant_vector<Ts...>;
std::array<var_vec, sizeof...(Ts)> vecs;
};
我们创建了一个变体向量,其中可以包含一个类型列表。您必须使用 boost::variant
来获取内容(这意味着知道内容的类型,或者写一个访问者)。
然后我们存储这些变体向量的数组,每种类型一个。
现在,如果您的 class 只保存一种类型的数据,您可以取消数组,只拥有一个 var_vec
.
类型的成员
我不明白你为什么想要每种类型的一个向量。我可以看到想要一个向量,其中每个元素都是任何类型之一。那将是 vector<variant<Ts...>>
,而不是上面的 variant<vector<Ts>...>
.
variant<Ts...>
是 boost
联合类型。 any
是boost
smart-void*
。 optional
是 boost
存在与否。
template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );
可能是一个有用的函数,它接受一个 any
并尝试将其转换为 variant
中的任何 Ts...
类型,并且 returns 如果它成功了(如果没有,returns 一个空的 optional
)。
如前所述,最好的方法是使用元组:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::tuple<std::vector<AcceptedTypes>...> vectors;
};
这是乘以 "fields" 的唯一方法,因为您不能神奇地让它拼出字段名称。另一件重要的事情可能是获得对它们的一些命名访问。我想你想要实现的是拥有多个具有 unique 类型的向量,因此你可以使用以下工具 "search" 通过其值类型获取正确的向量:
template <class T1, class T2>
struct SameType
{
static const bool value = false;
};
template<class T>
struct SameType<T, T>
{
static const bool value = true;
};
template <typename... Types>
class MyClass
{
public:
typedef std::tuple<vector<Types>...> vtype;
vtype vectors;
template<int N, typename T>
struct VectorOfType: SameType<T,
typename std::tuple_element<N, vtype>::type::value_type>
{ };
template <int N, class T, class Tuple,
bool Match = false> // this =false is only for clarity
struct MatchingField
{
static vector<T>& get(Tuple& tp)
{
// The "non-matching" version
return MatchingField<N+1, T, Tuple,
VectorOfType<N+1, T>::value>::get(tp);
}
};
template <int N, class T, class Tuple>
struct MatchingField<N, T, Tuple, true>
{
static vector<T>& get(Tuple& tp)
{
return std::get<N>(tp);
}
};
template <typename T>
vector<T>& access()
{
return MatchingField<0, T, vtype,
VectorOfType<0, T>::value>::get(vectors);
}
};
这是测试用例,您可以尝试一下:
int main( int argc, char** argv )
{
int twelf = 12.5;
typedef reference_wrapper<int> rint;
MyClass<float, rint> mc;
vector<rint>& i = mc.access<rint>();
i.push_back(twelf);
mc.access<float>().push_back(10.5);
cout << "Test:\n";
cout << "floats: " << mc.access<float>()[0] << endl;
cout << "ints: " << mc.access<rint>()[0] << endl;
//mc.access<double>();
return 0;
}
如果您使用任何不在您传递给 specialize MyClass 的类型列表中的类型(请参阅此注释掉的双精度访问),您将得到一个编译错误,不太可读,但 gcc 至少指出导致问题的正确位置并且至少这样的错误消息表明问题的正确原因 - 例如,如果您尝试执行 mc.access() :
error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
不使用元组的替代解决方案是使用 CRTP 创建一个 class 层次结构,其中每个基础 class 是一种类型的特化:
#include <iostream>
#include <string>
template<class L, class... R> class My_class;
template<class L>
class My_class<L>
{
public:
protected:
L get()
{
return val;
}
void set(const L new_val)
{
val = new_val;
}
private:
L val;
};
template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:
template<class T>
T Get()
{
return this->My_class<T>::get();
}
template<class T>
void Set(const T new_val)
{
this->My_class<T>::set(new_val);
}
};
int main(int, char**)
{
My_class<int, double, std::string> c;
c.Set<int>(4);
c.Set<double>(12.5);
c.Set<std::string>("Hello World");
std::cout << "int: " << c.Get<int>() << "\n";
std::cout << "double: " << c.Get<double>() << "\n";
std::cout << "string: " << c.Get<std::string>() << std::endl;
return 0;
}
有人提议允许这种扩展,语法直观:P1858R1 Generalized pack declaration and usage。您还可以初始化成员并通过索引访问它们。您甚至可以通过编写 using... tuple_element = /*...*/
:
来支持结构化绑定
template <typename... Ts>
class MyClass {
std::vector<Ts>... elems;
public:
using... tuple_element = std::vector<Ts>;
MyClass() = default;
explicit MyClass(std::vector<Ts>... args) noexcept
: elems(std::move(args))...
{
}
template <std::size_t I>
requires I < sizeof...(Ts)
auto& get() noexcept
{
return elems...[I];
}
template <std::size_t I>
requires I < sizeof...(Ts)
const auto& get() const
{
return elems...[I];
}
// ...
};
那么class可以这样使用:
using Vecs = MyClass<int, double>;
Vecs vecs{};
vecs.[0].resize(3, 42);
std::array<double, 4> arr{1.0, 2.0, 4.0, 8.0};
vecs.[1] = {arr.[:]};
// print the elements
// note the use of vecs.[:] and Vecs::[:]
(std::copy(vecs.[:].begin(), vecs.[:].end(),
std::ostream_iterator<Vecs::[:]>{std::cout, ' '},
std::cout << '\n'), ...);
我有一个模板 class,其中每个模板参数代表内部计算可以处理的一种类型的值。需要模板(而不是函数重载),因为值作为 boost::any 传递,并且它们的类型在运行前不明确。
为了正确转换为正确的类型,我希望每个可变参数类型都有一个成员列表,如下所示:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::vector<T1> m_argumentsOfType1;
std::vector<T2> m_argumentsOfType2; // ...
};
或者,我想将模板参数类型存储在一个列表中,以便用它做一些 RTTI 魔术 (?)。但是我也不清楚如何将它们保存在 std::initializer_list 成员中。
感谢您的帮助!
如 πìντα-ῥεῖ 的评论中所述,执行此类操作的一种方法是使用元组。他没有解释(可能是为了让你远离自己)的是它的样子。
这是一个例子:
using namespace std;
// define the abomination
template<typename...Types>
struct thing
{
thing(std::vector<Types>... args)
: _x { std::move(args)... }
{}
void print()
{
do_print_vectors(std::index_sequence_for<Types...>());
}
private:
template<std::size_t... Is>
void do_print_vectors(std::index_sequence<Is...>)
{
using swallow = int[];
(void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
}
template<class Vector>
void print_one(const Vector& v)
{
copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
cout << endl;
}
private:
tuple<std::vector<Types>...> _x;
};
// test it
BOOST_AUTO_TEST_CASE(play_tuples)
{
thing<int, double, string> t {
{ 1, 2, 3, },
{ 1.1, 2.2, 3.3 },
{ "one"s, "two"s, "three"s }
};
t.print();
}
预期输出:
1,2,3,
1.1,2.2,3.3,
one,two,three,
这里是使用 boost::variant
:
template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;
template<typename ...Ts>
struct MyClass {
using var_vec = variant_vector<Ts...>;
std::array<var_vec, sizeof...(Ts)> vecs;
};
我们创建了一个变体向量,其中可以包含一个类型列表。您必须使用 boost::variant
来获取内容(这意味着知道内容的类型,或者写一个访问者)。
然后我们存储这些变体向量的数组,每种类型一个。
现在,如果您的 class 只保存一种类型的数据,您可以取消数组,只拥有一个 var_vec
.
我不明白你为什么想要每种类型的一个向量。我可以看到想要一个向量,其中每个元素都是任何类型之一。那将是 vector<variant<Ts...>>
,而不是上面的 variant<vector<Ts>...>
.
variant<Ts...>
是 boost
联合类型。 any
是boost
smart-void*
。 optional
是 boost
存在与否。
template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );
可能是一个有用的函数,它接受一个 any
并尝试将其转换为 variant
中的任何 Ts...
类型,并且 returns 如果它成功了(如果没有,returns 一个空的 optional
)。
如前所述,最好的方法是使用元组:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::tuple<std::vector<AcceptedTypes>...> vectors;
};
这是乘以 "fields" 的唯一方法,因为您不能神奇地让它拼出字段名称。另一件重要的事情可能是获得对它们的一些命名访问。我想你想要实现的是拥有多个具有 unique 类型的向量,因此你可以使用以下工具 "search" 通过其值类型获取正确的向量:
template <class T1, class T2>
struct SameType
{
static const bool value = false;
};
template<class T>
struct SameType<T, T>
{
static const bool value = true;
};
template <typename... Types>
class MyClass
{
public:
typedef std::tuple<vector<Types>...> vtype;
vtype vectors;
template<int N, typename T>
struct VectorOfType: SameType<T,
typename std::tuple_element<N, vtype>::type::value_type>
{ };
template <int N, class T, class Tuple,
bool Match = false> // this =false is only for clarity
struct MatchingField
{
static vector<T>& get(Tuple& tp)
{
// The "non-matching" version
return MatchingField<N+1, T, Tuple,
VectorOfType<N+1, T>::value>::get(tp);
}
};
template <int N, class T, class Tuple>
struct MatchingField<N, T, Tuple, true>
{
static vector<T>& get(Tuple& tp)
{
return std::get<N>(tp);
}
};
template <typename T>
vector<T>& access()
{
return MatchingField<0, T, vtype,
VectorOfType<0, T>::value>::get(vectors);
}
};
这是测试用例,您可以尝试一下:
int main( int argc, char** argv )
{
int twelf = 12.5;
typedef reference_wrapper<int> rint;
MyClass<float, rint> mc;
vector<rint>& i = mc.access<rint>();
i.push_back(twelf);
mc.access<float>().push_back(10.5);
cout << "Test:\n";
cout << "floats: " << mc.access<float>()[0] << endl;
cout << "ints: " << mc.access<rint>()[0] << endl;
//mc.access<double>();
return 0;
}
如果您使用任何不在您传递给 specialize MyClass 的类型列表中的类型(请参阅此注释掉的双精度访问),您将得到一个编译错误,不太可读,但 gcc 至少指出导致问题的正确位置并且至少这样的错误消息表明问题的正确原因 - 例如,如果您尝试执行 mc.access
error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
不使用元组的替代解决方案是使用 CRTP 创建一个 class 层次结构,其中每个基础 class 是一种类型的特化:
#include <iostream>
#include <string>
template<class L, class... R> class My_class;
template<class L>
class My_class<L>
{
public:
protected:
L get()
{
return val;
}
void set(const L new_val)
{
val = new_val;
}
private:
L val;
};
template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:
template<class T>
T Get()
{
return this->My_class<T>::get();
}
template<class T>
void Set(const T new_val)
{
this->My_class<T>::set(new_val);
}
};
int main(int, char**)
{
My_class<int, double, std::string> c;
c.Set<int>(4);
c.Set<double>(12.5);
c.Set<std::string>("Hello World");
std::cout << "int: " << c.Get<int>() << "\n";
std::cout << "double: " << c.Get<double>() << "\n";
std::cout << "string: " << c.Get<std::string>() << std::endl;
return 0;
}
有人提议允许这种扩展,语法直观:P1858R1 Generalized pack declaration and usage。您还可以初始化成员并通过索引访问它们。您甚至可以通过编写 using... tuple_element = /*...*/
:
template <typename... Ts>
class MyClass {
std::vector<Ts>... elems;
public:
using... tuple_element = std::vector<Ts>;
MyClass() = default;
explicit MyClass(std::vector<Ts>... args) noexcept
: elems(std::move(args))...
{
}
template <std::size_t I>
requires I < sizeof...(Ts)
auto& get() noexcept
{
return elems...[I];
}
template <std::size_t I>
requires I < sizeof...(Ts)
const auto& get() const
{
return elems...[I];
}
// ...
};
那么class可以这样使用:
using Vecs = MyClass<int, double>;
Vecs vecs{};
vecs.[0].resize(3, 42);
std::array<double, 4> arr{1.0, 2.0, 4.0, 8.0};
vecs.[1] = {arr.[:]};
// print the elements
// note the use of vecs.[:] and Vecs::[:]
(std::copy(vecs.[:].begin(), vecs.[:].end(),
std::ostream_iterator<Vecs::[:]>{std::cout, ' '},
std::cout << '\n'), ...);