【问题标题】:Type alias a template parameter pack键入别名模板参数包
【发布时间】:2018-06-08 07:01:55
【问题描述】:

在下面的示例中,我基本上尝试为模板参数包起别名。

这在标准中是不可能的,所以我发现人们使用元组或空模板结构来解决这个限制。但是我的情况似乎有所不同,因为我没有要匹配的参数参数类型包...

我知道这个示例看起来很愚蠢,但它是一个最小的 PoC。在代码库中,类型列表变得更长,这真的很有用。

#include <iostream>
#include <tuple>

template<typename T>
void f(T t)
{
    std::cout << "templated f()" << std::endl;

}
template<>
void f(int t)
{
    std::cout << "specialized f()" << std::endl;
}

template<typename A, typename B>
void fn()
{
    f(A());
}

template<int, char>
void fn()
{
    int a;
    f(a);
}

// Can't seem to define this correctly.
//using MyType = std::tuple<int, char>()::type;

int
main(int, char**)
{
    fn<char, char>();

    // I have
    fn<int, char>();

    // I want some alias;
    //fn<MyType>();

    return 0;
}

参考资料;

【问题讨论】:

    标签: c++ templates variadic-templates template-meta-programming


    【解决方案1】:

    如果我们使用模板化的空结构来为类型包起别名,则可以在原始函数上使用 SFINAE,这样当为我们的特殊结构调用它时,它只会转发封装的类型包到原来的实现。

    这是 C++14 中的样子:

    template <typename... Types>
    struct TypePack {};
    
    // Helper meta functions to determine
    // if the template param is a TypePack struct.
    template <typename... Ts>
    static constexpr bool is_typepack_v = false;
    
    template <typename... Ts>
    static constexpr bool is_typepack_v<TypePack<Ts...>> = true;
    
    template <typename... Ts>
    // Enabled whenever template parameter is not a TypePack.
    typename std::enable_if<not is_typepack_v<Ts...>>::type 
    fn() {  // FN_A
        std::cout << "Generic\n";
    }
    
    template <>
    // Complete specialization, selected over generic fn for <char,int>
    void fn<char,int> () {  // FN_B
        std::cout << "Specialized for char,int\n";
    }
    
    template <typename T>
    // Enabled when template param is a TypePack.
    typename std::enable_if<is_typepack_v<T>>::type
    fn() { // FN_C
       forward_fn(T{}); 
    }
    
    // forward_fn() extracts the types from a TypePack argument
    // and invokes fn with it.
    template <typename ...T>
    void forward_fn(TypePack<T...> /*unused*/) {
        std::cout << "Forwarding a pack alias\n";
        fn<T...>();
    }
    
    // To alias a type pack use TypePack<T1,T2..,Tn>
    using CharIntPack = TypePack<char,int>;
    
    int main() {
        fn<char,char>(); // invokes FN_A
        fn<char,int>(); //  invokes FN_B
        fn<CharIntPack>(); // invokes FN_C which then invokes FN_B
        return 0;
    }
    

    这会产生以下输出:

    Generic
    Specialized for char,int
    Forwarding a pack alias
    Specialized for char,int
    

    我喜欢这个方法的地方在于,这个“trick”在定义函数时只需要一次,用户可以完全不知道。

    【讨论】:

      【解决方案2】:

      参数包必须就是这样,一个包含多种类型的列表。可以通过在其上使用 ... 运算符来转发参数包(一旦有),从而将其重新展开回列表,但您必须以这些类型开头。

      请注意: 使用 MyType = std::tuple()::type; 不会工作,因为元组没有“类型”成员。你甚至不能做(有或没有...): 模板 结构持有人 { typedef 类型...类型; }; 使用 my_type = holder::type;

      【讨论】:

        【解决方案3】:

        在下面的示例中,我尝试为模板参数包起别名 基本上。

        这在标准中是不可能的,所以我发现人们可以解决 元组或空模板结构的限制。

        std::tuple&lt;...&gt; 非常适合用于别名参数包

        但是我的情况似乎有所不同,因为我没有争论 要匹配的参数类型包...

        好吧,在这些情况下,您会发现 class-templates 实际上比 function-templates 更强大 - 在这种情况下,我们可以拥有 部分模板专业化;因此,如果您决定使用带有重载 operator () 的类模板,我确实说过,这是可能的:

        Demo:

        template<typename... A>
        struct fn
        {
            void operator () () const 
            { std::cout << "primary f()" << std::endl; }
        };
        
        template<typename... A>
        struct fn<std::tuple<A...>>
        {
            void operator () () const 
            { std::cout << "specialized on unlimited params()" << std::endl; }
        };
        
        template<>
        struct fn<std::tuple<char, double*>>
        {
            void operator () () const 
            { std::cout << "specialized on f<char, double*>()" << std::endl; }
        };
        
        template<typename A, typename B>
        struct fn<std::tuple<A, B>>
        {
            void operator () () const
            { std::cout << "specialized on two params f()" << std::endl; }
        };
        
        // Can't seem to define this correctly.
        using MyType = std::tuple<int, char>;
        using MyType2 = std::tuple<char, double*>;
        using MyType3 = std::tuple<int, char, double, void*>;
        
        int main()
        {
            fn<int, char>()();
            fn<MyType>()();
            fn<MyType2>()();
            fn<MyType3>()();
            return 0;
        }
        

        输出:

        primary f()
        specialized on two params f()
        specialized on f<char, double*>()
        specialized on unlimited params()
        

        【讨论】:

        • 谢谢,这是一个很好的解决方案,我只是不喜欢仿函数需要的接口,如果它们不是独立的函数而是类成员的话。您将拥有 object::fn() 而不是 object.fn()
        【解决方案4】:

        从 C++14 开始,如果您不想重载或重写您的函数,也不想编写仿函数类模板,您可以使用泛型 lambda 在本地将类型列表转换为参数包:

        template<typename T>
        struct quote{ using type = T; };
        
        template<typename T>
        using unpack = typename T::type;
        
        template<typename... T>
        using params = std::tuple<quote<T>...>;
        
        // to be used as
        
        template<typename A, typename B>
        void foo();
        
        using my_params = params<int,char>;
        
        int main()
        {
          std::apply( []( auto... params ){ foo< unpack<decltype(params)>... >(); }, my_params{} );
        }
        

        PS: std::apply 需要 C++17,但也可以在 >=C++11 中实现...

        PPS: 在 C++20 中,我们甚至可以写 []&lt;typename... T&gt;(){ foo&lt;T...&gt;(); } 使解决方案更加简洁...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-20
          • 2011-12-22
          • 1970-01-01
          相关资源
          最近更新 更多