这个答案的第一部分只是展示了如何在编译时对一系列类型进行编码和迭代,第二部分展示了如何在你的例子中使用它。
第 1 部分:模式选项 1
如果您在编译时需要所有内容,请查看选项 2,否则就足够了。
您可以使用如下所示的变体:
using MyPeelers = std::variant<Peeler1, Peeler2, Peeler3>;
然后像这样使用向量:
std::vector<MyPeelers> vec
{
Peeler1{},
Peeler2{},
Peeler3{}
};
注意:std::vector 不是“constexpr”,它的元素不会在编译时添加。为此,您可以使用 std::array (参见“第 2 部分:实施选项 2”)。
然后像这样遍历元素:
for(auto & v: vec)
{
std::visit([](auto arg)
{
std::cout << typeid(arg).name() << "\n";
}, v);
}
你可以在这里玩这个:https://onlinegdb.com/rJlqb1sVr
第 1 部分:模式选项 2
请注意,模式选项 2 可以在编译时完全评估(但如果您打印到控制台则不能...)。为此,您需要将水果向量更改为带有 Fruit 元素的 constexpr std::array。
对于剥离器,您可以使用元组。这个方法是完全编译时的。
constexpr std::tuple my_peelers{ Peeler1{}, Peeler2{}, Peeler3{} };
以下方法使用折叠表达式(参见https://en.cppreference.com/w/cpp/language/fold)。在这种情况下,超过逗号运算符。此折叠表达式分别遍历应用函数 lba 的参数中的每个元素。每个参数的类型可以通过在 args 上使用 decltype 来确定。
std::apply([](auto ... args)
{
((std::cout << typeid(args).name() << "\n" ), ...);
},
my_peelers);
你可以在这里玩它:https://onlinegdb.com/BkbTgJiES
第 2 部分:实施选项 1
这里的第一个区别,这里涉及到 std::variant:
using MyPeelers = std::variant<Peeler1, Peeler2, Peeler3>;
static const std::vector<MyPeelers> myPeelers
{
Peeler1{},
Peeler2{},
Peeler2{},
Peeler3{},
Peeler1{},
Peeler3{},
Peeler2{},
Peeler1{},
Peeler2{}
};
这个函数本质上没有改变,只是修改为打印模板参数和水果。
//
// Peeler function, takes peeler object as template parameter
template <typename T>
void peel(const Fruit& f)
{
std::cout << "Pealing " << ToString(f) << " with " << T::Name << "\n";
}
按值遍历所有不同的剥离器是衰减类型的一种巧妙方法。如果您不使用 v 的值,则不应有与此方法相关的开销。
在循环内部使用变量和访问者模式。将使用存储在 v 中的值调用 variables lambda 函数。调用后的“arg”的类型是存储在 v 中的类型。此方法导致为 variant 中的每个唯一类型生成一些代码。在这种情况下 Peeler1、Peeler2 和 Peeler3。我们可以通过在“arg”上使用 declval 来确定哪种类型。一旦我们有了类型,我们所需要的就是遍历水果。
//
// All by value, no values matter, only types
for(auto v : myPeelers)
{
std::visit
(
[](auto arg)
{
for (auto fruit : myFruits)
{
peel<decltype(arg)>(fruit);
}
},
v);
}
请看这里:https://onlinegdb.com/B1rf4Ji4H
上述程序的输出为:
Pealing Coconut with Peeler1
Pealing Avocado with Peeler1
Pealing Banana with Peeler1
Pealing Banana with Peeler1
Pealing Elderberr with Peeler1
Pealing Coconut with Peeler1
Pealing Date with Peeler1
Pealing Elderberr with Peeler1
Pealing Date with Peeler1
Pealing Avocado with Peeler1
Pealing Coconut with Peeler2
Pealing Avocado with Peeler2
Pealing Banana with Peeler2
...
这样持续了一段时间……
第 2 部分:实施选项 2
这是我更喜欢的实现,但你不能在运行时更改 peler 数组的大小,所以这可能很烦人。
除了两个地方之外,它的工作方式与以前相同:
pealers 现在被定义为一个元组:
static constexpr std::tuple myPeelers
{
Peeler1{},
Peeler2{},
Peeler2{},
Peeler3{},
Peeler1{},
Peeler3{},
Peeler2{},
Peeler1{},
Peeler2{}
};
为了确保在编译时定义所有内容,fruit 现在使用 std::array:
static constexpr std::array myFruits
{
Fruit::Coconut,
Fruit::Avocado,
Fruit::Banana,
Fruit::Banana,
Fruit::Elderberry,
Fruit::Coconut,
Fruit::Date,
Fruit::Elderberry,
Fruit::Date,
Fruit::Avocado
};
推导出元组的模板参数。我觉得这样更方便。
迭代如“第 1 部分:模式选项 2”中所述。在 args 参数上使用 decltype 获取元组中每个元素的类型。它使用类型作为剥离的模板参数。
std::apply([](auto ... args)
{
for (auto fruit : myFruits)
{
(peel<decltype(args)>(fruit), ...);
}
},
myPeelers);
你可以在这里运行它:https://onlinegdb.com/B1W4wysEB
输出是一样的。
程序集显示所有内容似乎都在编译时进行了评估(除了打印语句)。
https://godbolt.org/z/17u3v5
第 2 部分:解决方案选项 3 (C++11)
根据要求,我已将其改编为 C++11 解决方案。
第一个区别是 constexpr 已从所有函数中删除。
下一个区别是我不能再将值存储在元组中,元组是在 C++17 中发布的。但这肯定是个问题,只要把它编码在一个函数中就可以得到相同的模板参数类型推导。
第三个区别是它不再方便使用std::array,转换为Fruit[]:
static Fruit myFruits[]
{
Fruit::Coconut,
Fruit::Avocado,
Fruit::Banana,
Fruit::Banana,
Fruit::Elderberry,
Fruit::Coconut,
Fruit::Date,
Fruit::Elderberry,
Fruit::Date,
Fruit::Avocado
};
为了遍历每个 peler,我们使用可变参数模板和尾端递归。为了停止递归,我们有一个空的终端案例。
这是终端案例:
void Pealers()
{
}
这是正常情况:
template <typename Front, typename ... Args>
void Pealers(Front, Args ... args)
{
for (Fruit fruit : myFruits)
{
peel<Front>( fruit );
}
Pealers(args...);
}
要使用上述内容,请执行以下操作:
Pealers
(
Peeler1{},
Peeler2{},
Peeler2{},
Peeler3{},
Peeler1{},
Peeler3{},
Peeler2{},
Peeler1{},
Peeler2{}
);
代码可以在这里运行:https://onlinegdb.com/BkKapR2NS
该程序集可以在这里查看:https://godbolt.org/z/1psKDk
还有一个没有 print 语句的版本在这里显示完整的内联 https://godbolt.org/z/-2DewW