Variadic 函数模板:根据 N 个编译时间值在 运行 时间自动执行 N 个输入
Variadic function template: automating N inputs at run time based on N compile time value
在为我的一位 class 成员工作时,我 运行 遇到了绊脚石...
我将简要解释一下我的数据结构。我有两个一维向量,它们都被索引为二维数组结构。我的第一个 table 中的数据被组织为专栏专业。
我的 class 是一个采用两个整型参数的模板。这些都不是 table 的总大小。第一个参数是存储在输入 table 中的输入数。输入 table 的大小为 [N x 2^N]
,由第一个模板参数生成。第二个 table 是一个 [M x 2^N]
,其中 [N] 和 [M] 都是列数,[2^N] 是两者的行数。
第一个 table 的目的是为给定的 N-Input truth table 生成所有可能的值。例如,如果有 3 个输入,那么第一个 table 将有 3 列和 8 行。然后,如果 M 为 1,输出将有 1 列 8 行、2 列等 table.
我在内存中的数据向量如下所示:
Inputs: A, B, C; Outputs: X, Y
// input vector
{ a0 ... a7, b0 ... b7, c0 ... c7 }
// output vector
{ X0 ... X7, Y0 ... Y7 }
第一个table是自动生成的,我已经完成了。我还以并排的方式完成了这两个 table 的打印,其中输入在左边,输出在右边。
这组函数是可变参数模板,因为它们可以接受任意数量的参数。这里的可变参数类型是同类的,因为它们都是同一类型。
在我的 class 中,我将函数类型存储为向量中的枚举 class,我在 switch 语句中使用它来将适当的函数应用于每行的输入,并且这是我有点卡住的地方......
现在,对于我 class 的 apply()
函数中的问题,您可以在下面看到完整的 class,我能够轻松地索引到 output
table 设置所需的输出。我可以很容易地计算 input
table 中的初始索引,但我遇到的问题是如何将给定行中的 N 个输入中的每一个作为要应用的函数的参数传递?所有的值在编译时都是已知的,我只想自动将一行的输入作为单独的参数传递到输出中,例如考虑以下事实 table:
// Inputs: A, B Outputs: Y, Y = And
0 0 | 0
0 1 | 0
1 0 | 0
1 1 | 1
// Intputs: A, B, C Outputs X, Y X = Or Y = Xor
0 0 0 | 0 0
0 0 1 | 1 1
0 1 0 | 1 1
0 1 1 | 1 0
1 0 0 | 1 1
1 0 1 | 1 0
1 1 0 | 1 0
1 1 1 | 1 (0 or 1) // depending on interpretation of XOr: Single bit high=true or odd parity=true
// Here I'm using C++ operator ^ as the default intepretation!
因此,正如您在上面看到的,可以实例化此 class 模板,如上所示:分别为 BinaryTTGenerator<2,1>
和 BinaryTTGenerator<3,2>
。
我只需要知道如何能够为第一个应用 2 个输入,为第二个应用 3 个输入,其中要传递给相应函数的输入量由 N 定义。我愿意任何建议和可能性,如果可以做到!
下面是我的 class 中的 apply()
函数:
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index], ... ?); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index], ... ?); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index],... ?); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index], ... ?); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index], ... ?); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index], ... ?); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
此外,我不确定双循环是否需要在 switch 之外或在每个 case 语句中执行...
这是我目前的class:
#pragma once
// Binary Truth Table Generator
#include <algorithm>
#include <array>
#include <bitset>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
using u16 = std::uint16_t;
using Bit = std::bitset<1>;
// The Boolean Operational Functions:
enum class BFT {
BUFFER, // Not included within the switch statement of the class!
NOT, // Both Not and And are non variadic but can be applied
// directly to a specific input, or to another function
// as these both take in a `Bit` and return a `Bit` type.
AND, // The following types are all variadic as they can
OR, // have any number of arguments.
NAND,
NOR,
XOR,
XNOR
// Possible Future Implementations:
// Tristate Buffer and Tristate Controlled Buffer.
};
// Helper Templates
template <typename... Bits>
constexpr bool all_bits() {
return (std::is_same_v<Bits, Bit> && ...);
}
template <typename... FuncTypes>
constexpr bool all_same() {
return (std::is_same_v<FuncTypes, BFT> &&...);
}
// Unary Functions
auto Buffer(Bit& b) -> auto {
return b;
}
auto Not(Bit& b) -> auto {
return ~b;
}
// Binary Functions with multiple inputs.
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
And(Bits... bits) {
return (bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Or(Bits... bits) {
return (bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nand(Bits... bits) {
return ~(bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nor(Bits... bits) {
return ~(bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Xor(Bits... bits) {
return (bits^...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
XNor(Bits... bits) {
return ~(bits^...);
}
// N is the number of inputs where M is the number of functions performed on the set or row of N
template<u16 N, u16 M>
struct BinaryTTGenerator {
// Calculate the Number of Cols & Rows as well
// as the stride for indexing into the vector
// typically the stride should almost always
// equal that of the number of rows.
const u16 numCols_ = M + N;
const u16 numRows_ = 1U << N;
const u16 stride_ = numCols_;
// Calculate the grid sizes there are 2 different grids
// as well as the overall grid, which are loosely combined.
// The first grid is all of the possible combinations
// of the inputs, the second grid is the respective outputs
// to each applied function to the set of inputs on a specific
// row. The combined grid or table is that concatenation of the two
// with the input grid on the left and the output grid on the right.
const u16 inputGridSize_ = N * numRows_;
const u16 outputGridSize_ = M * numRows_;
std::vector<Bit> inputTable_ = std::vector<Bit>(inputGridSize_, Bit{ 0 });
std::vector<Bit> outputTable_ = std::vector<Bit>(outputGridSize_, Bit{ 0 });
std::vector<BFT> functionTypes_;
BinaryTTGenerator() = default;
explicit BinaryTTGenerator(BFT bft) : functionTypes_{ bft } {}
template<typename... FuncTypes>
BinaryTTGenerator(FuncTypes... funcs) {
/*static_assert((sizeof...(funcs) + 1) == M, "Aguments does not equal the number of functions");
static_assert(std::is_same<
std::integer_sequence<bool, true, std::is_same<BFT, std::remove_reference_t<First>>::value>,
std::integer_sequence<bool, std::is_same<BFT, std::remove_reference_t<First>>::value, true >
> ::value, "!");
static_assert(std::is_same<
std::integer_sequence<bool, true, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)...>,
std::integer_sequence<bool, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)..., true>
>::value, "!");*/
functionTypes_{ funcs... };
}
// initializes all of the input values
void initialize() {
u16 fill = 1U << (N - 1);
for (u16 col = 0; col < N; ++col, fill >>= 1U) {
for (u16 row = fill; row < (1U << N); row += (fill * 2)) {
u16 index = col*numRows_ + row;
std::fill_n(&inputTable_[index], fill, 1);
};
}
}
// apply the set of M functions individually on the N(row) of inputs.
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index]); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index]); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index]); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index]); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index]); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index]); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
void show() {
for (u16 y = 0; y < numRows_; ++y) { // y - height
for (u16 x = 0; x < numCols_; ++x) { // x - width
if (x < N) {
// The index variables are not necessary - I don't mind the extra variable.
// I'm using it for readability that pertains to the index value of a container.
// It is also easier to adjust or fix the equation to calculate the appropriate
// index value into the desired container.
std::size_t index = x * numRows_ + y;
std::cout << inputTable_[index].to_string() << " ";
} else {
std::size_t index = y * M + x - N;
std::cout << outputTable_[index].to_string() << " ";
}
}
std::cout << '\n';
}
}
};
类似的东西(未测试):
template <std::size_t... I>
void apply_impl(std::index_sequence<I...>) {
// ...
case BFT::AND:
outputTable_[f] = And(inputTable_[index + I]...); break;
// ...
}
void apply() {
return apply_impl(std::make_index_sequence<N>());
}
在为我的一位 class 成员工作时,我 运行 遇到了绊脚石...
我将简要解释一下我的数据结构。我有两个一维向量,它们都被索引为二维数组结构。我的第一个 table 中的数据被组织为专栏专业。
我的 class 是一个采用两个整型参数的模板。这些都不是 table 的总大小。第一个参数是存储在输入 table 中的输入数。输入 table 的大小为 [N x 2^N]
,由第一个模板参数生成。第二个 table 是一个 [M x 2^N]
,其中 [N] 和 [M] 都是列数,[2^N] 是两者的行数。
第一个 table 的目的是为给定的 N-Input truth table 生成所有可能的值。例如,如果有 3 个输入,那么第一个 table 将有 3 列和 8 行。然后,如果 M 为 1,输出将有 1 列 8 行、2 列等 table.
我在内存中的数据向量如下所示:
Inputs: A, B, C; Outputs: X, Y
// input vector
{ a0 ... a7, b0 ... b7, c0 ... c7 }
// output vector
{ X0 ... X7, Y0 ... Y7 }
第一个table是自动生成的,我已经完成了。我还以并排的方式完成了这两个 table 的打印,其中输入在左边,输出在右边。
这组函数是可变参数模板,因为它们可以接受任意数量的参数。这里的可变参数类型是同类的,因为它们都是同一类型。
在我的 class 中,我将函数类型存储为向量中的枚举 class,我在 switch 语句中使用它来将适当的函数应用于每行的输入,并且这是我有点卡住的地方......
现在,对于我 class 的 apply()
函数中的问题,您可以在下面看到完整的 class,我能够轻松地索引到 output
table 设置所需的输出。我可以很容易地计算 input
table 中的初始索引,但我遇到的问题是如何将给定行中的 N 个输入中的每一个作为要应用的函数的参数传递?所有的值在编译时都是已知的,我只想自动将一行的输入作为单独的参数传递到输出中,例如考虑以下事实 table:
// Inputs: A, B Outputs: Y, Y = And
0 0 | 0
0 1 | 0
1 0 | 0
1 1 | 1
// Intputs: A, B, C Outputs X, Y X = Or Y = Xor
0 0 0 | 0 0
0 0 1 | 1 1
0 1 0 | 1 1
0 1 1 | 1 0
1 0 0 | 1 1
1 0 1 | 1 0
1 1 0 | 1 0
1 1 1 | 1 (0 or 1) // depending on interpretation of XOr: Single bit high=true or odd parity=true
// Here I'm using C++ operator ^ as the default intepretation!
因此,正如您在上面看到的,可以实例化此 class 模板,如上所示:分别为 BinaryTTGenerator<2,1>
和 BinaryTTGenerator<3,2>
。
我只需要知道如何能够为第一个应用 2 个输入,为第二个应用 3 个输入,其中要传递给相应函数的输入量由 N 定义。我愿意任何建议和可能性,如果可以做到!
下面是我的 class 中的 apply()
函数:
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index], ... ?); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index], ... ?); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index],... ?); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index], ... ?); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index], ... ?); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index], ... ?); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
此外,我不确定双循环是否需要在 switch 之外或在每个 case 语句中执行...
这是我目前的class:
#pragma once
// Binary Truth Table Generator
#include <algorithm>
#include <array>
#include <bitset>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
using u16 = std::uint16_t;
using Bit = std::bitset<1>;
// The Boolean Operational Functions:
enum class BFT {
BUFFER, // Not included within the switch statement of the class!
NOT, // Both Not and And are non variadic but can be applied
// directly to a specific input, or to another function
// as these both take in a `Bit` and return a `Bit` type.
AND, // The following types are all variadic as they can
OR, // have any number of arguments.
NAND,
NOR,
XOR,
XNOR
// Possible Future Implementations:
// Tristate Buffer and Tristate Controlled Buffer.
};
// Helper Templates
template <typename... Bits>
constexpr bool all_bits() {
return (std::is_same_v<Bits, Bit> && ...);
}
template <typename... FuncTypes>
constexpr bool all_same() {
return (std::is_same_v<FuncTypes, BFT> &&...);
}
// Unary Functions
auto Buffer(Bit& b) -> auto {
return b;
}
auto Not(Bit& b) -> auto {
return ~b;
}
// Binary Functions with multiple inputs.
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
And(Bits... bits) {
return (bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Or(Bits... bits) {
return (bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nand(Bits... bits) {
return ~(bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nor(Bits... bits) {
return ~(bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Xor(Bits... bits) {
return (bits^...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
XNor(Bits... bits) {
return ~(bits^...);
}
// N is the number of inputs where M is the number of functions performed on the set or row of N
template<u16 N, u16 M>
struct BinaryTTGenerator {
// Calculate the Number of Cols & Rows as well
// as the stride for indexing into the vector
// typically the stride should almost always
// equal that of the number of rows.
const u16 numCols_ = M + N;
const u16 numRows_ = 1U << N;
const u16 stride_ = numCols_;
// Calculate the grid sizes there are 2 different grids
// as well as the overall grid, which are loosely combined.
// The first grid is all of the possible combinations
// of the inputs, the second grid is the respective outputs
// to each applied function to the set of inputs on a specific
// row. The combined grid or table is that concatenation of the two
// with the input grid on the left and the output grid on the right.
const u16 inputGridSize_ = N * numRows_;
const u16 outputGridSize_ = M * numRows_;
std::vector<Bit> inputTable_ = std::vector<Bit>(inputGridSize_, Bit{ 0 });
std::vector<Bit> outputTable_ = std::vector<Bit>(outputGridSize_, Bit{ 0 });
std::vector<BFT> functionTypes_;
BinaryTTGenerator() = default;
explicit BinaryTTGenerator(BFT bft) : functionTypes_{ bft } {}
template<typename... FuncTypes>
BinaryTTGenerator(FuncTypes... funcs) {
/*static_assert((sizeof...(funcs) + 1) == M, "Aguments does not equal the number of functions");
static_assert(std::is_same<
std::integer_sequence<bool, true, std::is_same<BFT, std::remove_reference_t<First>>::value>,
std::integer_sequence<bool, std::is_same<BFT, std::remove_reference_t<First>>::value, true >
> ::value, "!");
static_assert(std::is_same<
std::integer_sequence<bool, true, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)...>,
std::integer_sequence<bool, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)..., true>
>::value, "!");*/
functionTypes_{ funcs... };
}
// initializes all of the input values
void initialize() {
u16 fill = 1U << (N - 1);
for (u16 col = 0; col < N; ++col, fill >>= 1U) {
for (u16 row = fill; row < (1U << N); row += (fill * 2)) {
u16 index = col*numRows_ + row;
std::fill_n(&inputTable_[index], fill, 1);
};
}
}
// apply the set of M functions individually on the N(row) of inputs.
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index]); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index]); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index]); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index]); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index]); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index]); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
void show() {
for (u16 y = 0; y < numRows_; ++y) { // y - height
for (u16 x = 0; x < numCols_; ++x) { // x - width
if (x < N) {
// The index variables are not necessary - I don't mind the extra variable.
// I'm using it for readability that pertains to the index value of a container.
// It is also easier to adjust or fix the equation to calculate the appropriate
// index value into the desired container.
std::size_t index = x * numRows_ + y;
std::cout << inputTable_[index].to_string() << " ";
} else {
std::size_t index = y * M + x - N;
std::cout << outputTable_[index].to_string() << " ";
}
}
std::cout << '\n';
}
}
};
类似的东西(未测试):
template <std::size_t... I>
void apply_impl(std::index_sequence<I...>) {
// ...
case BFT::AND:
outputTable_[f] = And(inputTable_[index + I]...); break;
// ...
}
void apply() {
return apply_impl(std::make_index_sequence<N>());
}