【问题标题】:Variadic function template: automating N inputs at run time based on N compile time value可变参数函数模板:在运行时根据 N 个编译时间值自动执行 N 个输入
【发布时间】:2019-12-13 11:54:16
【问题描述】:

在为我的一位班级成员工作时,我遇到了一个绊脚石......

我将简要解释一下我的数据结构。我有两个一维向量,它们都被索引为二维数组结构。我的第一个表中的数据被组织为主要列。

我的类是一个接受两个整数类型参数的模板。这些不是任一表的整体大小。第一个参数是存储在输入表中的输入数。输入表的大小为[N x 2^N],由第一个模板参数生成。第二个表是[M x 2^N],其中 [N] 和 [M] 都是列数,[2^N] 是两者的行数。

第一个表的目的是为给定的 N 输入真值表生成所有可能的值。例如,如果有 3 个输入,则第一个表将有 3 列和 8 行。那么如果 M 为 1,输出表将有 1 列,8 行,2 列,依此类推。

我在内存中的数据向量如下所示:

Inputs: A, B, C; Outputs: X, Y

// input vector
{ a0 ... a7, b0 ... b7, c0 ... c7 }

// output vector
{ X0 ... X7, Y0 ... Y7 }       

第一个表是自动生成的,我已经完成了这么多。我还以并排的方式完成了这两个表的打印输出,其中输入在左侧,输出在右侧。

函数集是可变参数模板,因为它们可以接受任意数量的参数。这里的可变参数类型是同质的,因为它们都是相同的类型。

在我的类中,我将函数类型存储为向量中的枚举类,我在 switch 语句中使用它来将适当的函数应用于每行的输入,这就是我有点卡住的地方。 .


现在对于我在班级的apply() 函数中的问题,您可以在下面看到完整的班级,我可以轻松地索引output 表来设置所需的输出。我可以很容易地将初始索引计算到input 表中,但是我卡住的地方是如何将给定行中的每个 N 个输入作为参数传递给要应用的函数?所有值在编译时都是已知的,我只想自动将行的输入作为单独的参数传递到输出中,例如考虑以下真值表:

// 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!

如您在上面看到的,可以实例化这个类模板,如上所示:分别为BinaryTTGenerator<2,1>BinaryTTGenerator<3,2>

我只需要知道如何为第一个应用 2 个输入,为第二个应用 3 个输入,其中要传递给相应函数的输入数量由 N 定义。我愿意接受任何建议以及如果可以做到的可能性!

下面是我的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 语句中执行...


这是我目前的课程:

#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';
        }
    }

};

【问题讨论】:

  • 1 xor 1 xor 1 明确为 1。
  • @L.F.哦,好吧,我不确定...
  • @L.F.所以看起来 C++ 使用奇偶校验约定,而不仅仅是独占位..
  • 1 xor 1 xor 1 = (1 xor 1) xor 1 = 0 xor 1 = 1。“仅排他位”是什么意思?
  • 嗯...好吧,那么忽略我之前的评论。

标签: c++ parameter-passing c++17 variadic


【解决方案1】:

类似的东西(未测试):

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

【讨论】:

  • index_sequence 会从 0 计数到 N-1 吗?
  • 是的,有效。
  • 嗯,看了这个; index_sequence 可能会起作用,但是因为我的数据在主要列中,所以 3 个输入的位置各相距 8 个。示例:在 3x2 模板中,有 3 个输入,因此有 8 行。在向量中,首先是第 1 列,然后是第 2 列,然后是第 3 列。因此,当我对每一行进行迭代以将 3 个输入应用于函数时,我需要索引如下:R0{0,8,16},R1{1,9,17},R2{2,10, 18},R3{3,11,19},R4{4,12,20},R5{5,13,​​21},R6{6,14,22},R7{7,15,23} 为输入表的索引。 ...
  • 同时,输出表的索引会有所不同,因为它只有 2 列。
  • 你可以做index + I*(1&lt;&lt;N) 什么的。我们将计算算术作为练习留给读者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-13
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
  • 2014-08-01
相关资源
最近更新 更多