【问题标题】:C++ - How to call a templated method of a recursively inherited templated base classC++ - 如何调用递归继承的模板化基类的模板化方法
【发布时间】:2021-02-12 23:10:28
【问题描述】:

我正在尝试创建一个 Tuple 类,其中包含任意数量的任意类型的条目,使用可变参数模板并能够使用模板化的 entry 方法获取 nth 条目,因此我可以使用它如下:

Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"

我目前的做法:

template<typename ...Types>
struct Tuple;

template<typename Type>
struct Tuple<Type>
{
    Tuple(Type value) : value(value) { };
    Type value;

    template<int Index>
    Type& entry()
    {
        return value;
    }
};

template<typename Type, typename... Types>
struct Tuple<Type, Types...> : public Tuple<Types...>
{
    Tuple(Type value, Types ...args) : Tuple<Types...>(args...), value(value) { }
    Type value;

    template<int Index>
    auto entry() -> decltype(Tuple<Types...>::entry<Index-1>())&
    {
        return Tuple<Types...>::entry<Index-1>();
    }

    template<>
    Type& entry<0>() 
    {
        return value;
    }
};

第一个结构为一个元素提供“基本”情况,第二个结构在此基础上递归构建。但是,我得到了错误

In member function ‘decltype (((Tuple<Types ...>::entry < (Index - 1)) > <expression error>))& Tuple<Type, Types ...>::entry()’:
error: expected primary-expression before ‘)’ token

如何调用模板化基类的模板化方法?

【问题讨论】:

  • struct Tuple 的初始声明在哪里?这些都是专业。

标签: c++ templates variadic-templates template-meta-programming non-type-template-parameter


【解决方案1】:

假设您至少使用 C++14,您可以使用 auto&amp; 而不是尾随返回类型,并且正如 max66 所指出的,您需要在成员函数调用之前添加 template 关键字在你的语法中。

你也可以简化你的定义,因为你只需要一个空的基类,而不是一个为一种类型实现特化的类;您的第二个类已经实现了一种类型的必要行为。这种简化要求您使用std::enable_if 重写entry(),如果您使用的是C++17,则需要if constexpr

// only needed for C++14 when using std::enable_if
#include <type_traits>

template<typename...>
struct Tuple
{
};

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    Tuple(T value, Ts ...args) : value(value), Tuple<Ts...>(args...) { }
    T value;

    template<std::size_t I, std::enable_if_t<I == 0, bool> = true>
    T& entry() { return value; }

    template<std::size_t I, std::enable_if_t<I != 0, bool> = true>
    auto& entry() { return Tuple<Ts...>::template entry<I - 1>(); }

    // // requires C++17 support
    // template<std::size_t I>
    // auto& entry() {
    //     if constexpr (I == 0) { return value; }
    //     else { return Tuple<Ts...>::template entry<I - 1>(); }
    // }
};

试试godbolt.org

【讨论】:

    【解决方案2】:

    您需要在::entry 之前添加一个template

    template<int Index> // ...................VVVVVVVVV
    auto entry() -> decltype(Tuple<Types...>::template entry<Index-1>())&
    { // .......................VVVVVVVVV
        return Tuple<Types...>::template entry<Index-1>();
    }
    

    或者::entry之后的&lt;被解析为关系运算符。

    但是你还有一个问题:entry()的特化:

    template<>
    Type& entry<0>() 
    {
        return value;
    }
    

    不幸的是,如果不专门化包含类,您就无法专门化 a 方法。

    如果可以编译C++17,可以避免方法特化,使用if constexpr

    template <int Index>
    auto & entry()
     {
       if constexpr ( Index == 0 )
          return value;
       else 
          return Tuple<Types...>::template entry<Index-1>();
     }
    

    Pre C++17 在 C++14... 我想你可以使用标签调度来解决

    template <int>
    Type & entry_helper (std::true_type)
     { return value; }
    
    template <int Index>
    auto & entry_helper (std::false_type)
     { return Tuple<Types...>::template entry<Index-1>(); }
    
    template <int Index>
    auto & entry()
    { return entry_helper<Index>(std::integral_constant<bool, Index==0>{}); }
    

    在 C++11 中,您还需要 entry() 和第二个 entry_helper() 的尾随返回类型

    正如 Patrick Roberts 所指出的(谢谢!),添加尾随返回类型的解决方案适用于带有 g++ 的 C++11,但不适用于 clang++,因为在递归上下文中检测返回类型时存在问题。

    对于 C++11,我提出了一个完全不同的解决方案,它避免了 entry()/entry_helper() 递归,但在类级别添加了另一级间接(添加递归基类结构 Tpl)。还为entry()entry_helper() 添加完美转发、无符号索引和常量版本。

    #include <utility>
    #include <iostream>
    #include <type_traits>
    
    template <std::size_t, typename...>
    struct Tpl
     { void entry_helper () {} };
    
    template <std::size_t I, typename T, typename ... Ts>
    struct Tpl<I, T, Ts...> : public Tpl<I+1u, Ts...>
     {
       using Tpl<I+1, Ts...>::entry_helper;
    
       Tpl (T && t, Ts && ... ts)
          : Tpl<I+1u, Ts...>{std::forward<Ts>(ts)...}, value{std::forward<T>(t)}
        { }
    
       T value;
    
       T & entry_helper (std::integral_constant<std::size_t, I>)
        { return value; }
    
       T const & entry_helper (std::integral_constant<std::size_t, I>) const
        { return value; }
     };
    
    template <typename ... Ts>
    struct Tuple : public Tpl<0, Ts...>
     {
       using Tpl<0, Ts...>::entry_helper;
    
       Tuple (Ts && ... ts) : Tpl<0u, Ts...>{std::forward<Ts>(ts)...}
        { }
    
       template <std::size_t I>
       auto entry ()
        -> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) &
        { return entry_helper(std::integral_constant<std::size_t, I>{}); }
    
       template <std::size_t I>
       auto entry () const
        -> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) const &
        { return entry_helper(std::integral_constant<std::size_t, I>{}); }
     };
    
    int main()
     {
       Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
    
       std::cout << t.entry<1>() << std::endl; // Prints 2
       std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
     }
    

    【讨论】:

    • 我掉进了与 C++11 兼容的尾随返回类型的兔子洞,无论我尝试什么,我只得到了no matching member function for call to 'entry'(请参阅here)。我确实注意到,使用std::integral_constant&lt;int, Index&gt; 似乎是调度entry_helper 重载的更干净的选择。
    • @PatrickRoberts - 有趣...我用 clang++ 重现了错误,但 g++ 编译没有问题...我不明白谁是对的。
    • @PatrickRoberts - 可以肯定的是,我添加了一个特定于 C++11 的解决方案,以避免在方法级别的递归(但添加了一个间接级别......不如其他人优雅, C++14 和 C++17,解决方案)。
    • 哇,干得好,你真的应该得到接受的答案标记......
    • @PatrickRoberts - Naaaa...不太好。你的回答也值得被接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-19
    • 2016-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多