如何在没有辅助功能模板的情况下检索可变参数模板参数?

时间:2019-05-02 06:42:36

标签: c++ templates variadic-templates

假设我有

template<int ...>
struct Ints { };

class MyClass
{
public:
    Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};

我想做的很简单。

template <class T>
vector<int> MyFunc1(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

有点像这样。 (这里MyClass可能是T的一个示例。)显然,对于编译器S...似乎无效。

template <class T, int... S>
vector<int> MyFunc2(T& x)
{
    Ints<S...> result = x.get();
    vector<int> t = { S... };
    return t;
}

这也不起作用。我认为get()S...开始变得特定并且可以自动推论,但是编译器无法识别。 (我不确定,但C ++不会推断出在函数内部查找的模板参数,而只能推断出参数和返回类型)

我发现的唯一方法是使用另一个找出int...是什么的函数。

template <int ...S>
vector<int> temp(Ints<S...> not_used)
{
    return { S... };
}

template <class T>
vector<int> MyFunc3(T& x)
{
    auto result = x.get();
    return temp(result);
}

它运作良好,但需要另一个额外的辅助函数,该函数什么也不做,只是提供了语法上清晰的方式来使用模板匹配S...

我真的想仅通过单个功能来执行此操作。我真的真的必须每次都想检索参数包时定义辅助功能吗?

编辑:IntsMyFunc只是玩具示例。我想知道检索模板参数的一般方法!

4 个答案:

答案 0 :(得分:7)

理想的界面是什么样的?

如果给定类型为Ints<S...>的变量,则理想情况下我们可以使用S...并进行尽可能少的修改。

在这种情况下,我们可以设计一个接口,该接口允许我们将参数包用作可变参数或lambda的输入,甚至可以将这些值用作模板参数。

建议的接口[作为值传递的动态大小写/整数]

静态案例和动态案例都有相似的接口,但是动态案例更简洁一些,因此可以更好地进行介绍。给定变量和函数,我们将函数与变量定义中包含的参数包一起应用。

Ints<1, 2, 3> ints;

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = ints | [](auto... S) { return std::vector {S...}; };

// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S...}; }; 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = ints | [](auto... S) { return std::make_tuple(S...); };

// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S + ...); }; 

这是一种简单的统一语法,使我们可以使用S并将其用作参数包。

编写此界面

这部分也很简单。我们使用类型为Ints<S...>的变量和一个函数,并使用S...来应用该函数。

template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
    return f(S...); 
}

建议的接口[静态大小写/整数可用作模板参数]

如前所述,静态案例与动态案例具有相似的界面,并且从概念上讲不会太多。从用户的角度来看,唯一的区别是我们S... S.value ...`作为参数包而不是使用ll use作为参数包。

对于每个值,我们希望将其封装在以值为模板的相应类型中。这使我们可以在constexpr上下文中访问它。

template<int Value>
struct ConstInt {
    constexpr static int value = Value;
};

为了将其与动态情况区分开来,我将重载/而不是|。否则,它们的行为类似。除了将值包装在ConstInt类中,并且每个值都有其自己的类型之外,该实现与动态情况几乎相同。

template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
    return func(ConstInt<S>()...); 
}

静态使用此接口

C ++允许我们使用与非静态成员相同的语法访问类的静态成员,而不会失去constexpr状态。

假设我有一些ConstInt,其值为10。我可以直接使用I.value作为模板参数,也可以使用decltype(I)::value

// This is what'll be passed in as a parameter
ConstInt<10> I;

std::array<int, I.value> arr1;
std::array<int, decltype(I)::value> arr2; 
// Both have length 10

因此,扩展参数包非常简单,并且最终与动态情况几乎相同,唯一的区别是附加到.value的{​​{1}}。下面显示的是动态案例的示例,这次使用静态案例语法:

S

那么有什么新消息? 由于Ints<1, 2, 3> ints; // Get a vector from ints auto vec = ints | [](auto... S) { return std::vector {S.value...}; }; // Get an array from ints // arr = {1, 2, 3} auto arr = ints | [](auto... S) { return std::array {S.value...}; }; // Get a tuple from ints auto tup = ints | [](auto... S) { return std::make_tuple(S.value...); }; // Get sum of ints using a fold expression auto sum = ints | [](auto... S) { return (S.value + ...); }; 是constexpr,value可以用作模板参数。在本示例中,我们使用S.value使用{{1 }}:

S.value

在此示例中,我们将序列中的每个元素平方,然后返回一个新序列:

std::get

避免操作员重载的替代解决方案

如果要避免运算符重载,我们可以从函数式编程中汲取灵感,并使用auto tupA = std::make_tuple(10.0, "Hello", 3); auto indicies = Ints<2, 0, 1>{}; // tupB = {3, 10.0, "Hello"} auto tupB = indicies / [&](auto... S) { return std::make_tuple(std::get<S.value>(tupA)...); }; 函数处理事物,其编写方式如下:

auto ints = Ints<0, 1, 2, 3, 4, 5>(); 

// ints_squared = Ints<0, 1, 4, 9, 16, 25>(); 
auto ints_squared = ints / [](auto... S) {
    return Ints<(S.value * S.value)...>(); 
};

unpack是什么??此函数采用一堆值,并返回一个函数,该函数采用另一个函数并将该函数与vals一起用作输入。

通过template<int... vals> auto unpack(Ints<vals...>) { return [](auto&& f) { return f(vals...); }; } // Static case template<int... vals> auto unpack_static(Ints<vals...>) { return [](auto&& f) { return f(ConstInt<vals>()...); }; } 函数,我们可以将这些值作为参数应用到其他函数。

我们可以将结果分配给名为unpack的变量,然后可以使用unpack处理所有特定用例:

apply_ints

我们可以使用apply_ints重新编写以前的示例:

Ints<1, 2, 3> ints; //this variable has our ints

auto apply_ints = unpack(ints); // We use this function to unpack them

附录

本附录简要概述了如何更广泛地使用此语法(例如,当使用多个单独的参数包时)。

奖金示例:将两个单独包装中的值配对

为使您更好地了解此接口的灵活性,在下面的示例中,我们使用它来配对来自两个单独包装的值。

apply_ints

注意::MSVC和GCC都可以毫无问题地编译此示例,但是c不休。我认为MSVC和GCC是正确的,但我不确定。

奖励示例:获取二维时间表

此示例稍微复杂一点,但是我们还可以创建值的二维数组,这些数组从单独包中的所有值组合中提取。

在这种情况下,我用它来创建时间表。

// Get a vector from ints
// vec = {1, 2, 3}
auto vec = apply_ints([](auto... S) { return std::vector {S...}; });

// Get an array from ints
// arr = {1, 2, 3}
auto arr = apply_ints([](auto... S) { return std::array {S...}; }); 

// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = apply_ints([](auto... S) { return std::make_tuple(S...); });

// Get sum of ints using a fold expression
auto sum = apply_ints([](auto... S) { return (S + ...); }); 

答案 1 :(得分:6)

在C ++ 2a中,您可以使用模板化的lambda在函数内定义您的助手,例如:

auto v = []<std::size_t...Is>(std::index_sequence<Is...>){return std::vector{Is...};}(seq);
//         ^^^^^^^^^^^^^^^^^^ New in C++2a

Demo

答案 2 :(得分:1)

我建议向struct Int添加函数以获取不同的表示形式

#include <vector>
#include <array>

template<int ...values>
struct Ints {
    auto getAsVector() const {
        return std::vector<int>({ values... });
    }

    constexpr auto getAsArray() const {
        return std::array<int, sizeof...(values)>({ values... });
    }
};

class MyClass
{
public:
    Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};

int main() {
    MyClass a;
    auto array = a.get().getAsVector();
    return array.size();

}

答案 3 :(得分:1)

如果您不使用/创建帮助模板,则需要其他方式来提供值。

我想到的最简单,规范和通用的方法是将它们放在相同的类范围内,以便您的Ints结构变为:

template<int ...ints>
struct Ints {
  constexpr static std::initializer_list<int> vals = {ints...};
};

由于它是constexpr,因此应在编译时进行评估,并且不会产生运行时成本。 现在您将可以执行以下操作:

return std::vector<int>(ints.vals);