【问题标题】:C++ Forward non-template member function call to template functionC++将非模板成员函数调用转发到模板函数
【发布时间】:2012-02-05 14:56:08
【问题描述】:

我想在我的“记录”类中隐藏一个 std::tuple 并在其上提供一个 operator[] 来访问元组的元素。不会编译的天真代码是这样的:

#include <tuple>

template <typename... Fields>
class Record {
  private:
    std::tuple<Fields...> list;

  public:
    Record() {}

    auto operator[](std::size_t n)
            -> decltype(std::get<1u>(list)) {
        return std::get<n>(list);
    }
};

int main() {
    Record<int, double> r;
    r[0];
    return 0;
}

g++ 4.6 说:

x.cc:13:32: error: no matching function for call to ‘get(std::tuple<int, double>&)’
x.cc:13:32: note: candidates are:
/usr/include/c++/4.6/utility:133:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/utility:138:5: note: template<unsigned int _Int, class _Tp1, class _Tp2> const typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(const std::pair<_Tp1, _Tp2>&)
/usr/include/c++/4.6/tuple:531:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(std::tuple<_Elements ...>&)
/usr/include/c++/4.6/tuple:538:5: note: template<unsigned int __i, class ... _Elements> typename std::__add_c_ref<typename std::tuple_element<__i, std::tuple<_Elements ...> >::type>::type std::get(const std::tuple<_Elements ...>&)

基本上我想像在数组上一样调用Record::operator[]。这可能吗?

【问题讨论】:

    标签: c++ function templates c++11 tuples


    【解决方案1】:

    get 的参数是一个编译时间常数。您不能使用 运行时变量,你不能有一个函数 返回 tuple 成员,因为您的返回类型将是 错误的。你可以做的是滥用非类型参数推导:

    #include <tuple>
    
    template<typename... Args>
    struct Foo {
      std::tuple<Args...> t;
    
      template<typename T, std::size_t i>
      auto operator[](T (&)[i]) -> decltype(std::get<i>(t)) {
        return std::get<i>(t);
      }
      // also a const version
    };
    
    int main()
    {
      Foo<int, double> f;
      int b[1];
      f[b];
      return 0;
    }
    

    这太可怕了,我永远不会使用它,而且它对用户没有多大意义。我会通过模板成员转发get

    我将尝试解释为什么我认为这真的很邪恶:函数的返回类型仅取决于编译时事实(这对于virtual 成员函数略有变化)。让我们假设在某些情况下非类型参数推导是可能的(函数调用参数是constexpr)或者我们可以构建一些可以很好地隐藏它的东西,您的用户不会意识到他们的返回类型只是改变和隐含皈依会对他们造成恶劣的影响。明确表示可以避免一些麻烦。

    【讨论】:

    • @Lol4t0 最后一点很好。为什么要将模板参数 MyT::get 更改为 MyT::get(size_t i) ?您的用户真的会期望 [i] 根据 i 有不同的类型吗?
    • @JohanLundberg 抱歉,我不明白。这是针对我的吗?
    • 啊,不,我同意你的看法。我上一篇文章中的“你”本来就是OP。
    【解决方案2】:

    错误消息似乎具有误导性,因为您的代码问题非常清楚:

     auto operator[](std::size_t n)
                -> decltype(std::get<1u>(list)) {
            return std::get<n>(list);
        }
    

    nstd::get 的模板参数必须是一个常量表达式,但在上面的代码中 n不是一个常量表达式。

    【讨论】:

      【解决方案3】:

      不能使用运行时绑定的参数(如函数参数)作为模板参数,因为需要在编译时绑定。

      但是让我们想象一下它是:

      Record<Apple, Orange> fruitBasket;
      

      那么我们会有:

      • decltype(fruitBasket[0]) 等于 Apple
      • decltype(fruitBasket[1]) 等于 Orange

      这里没有让你烦恼的事情吗?

      在 C++ 中,函数签名由其参数的类型(以及可选的模板参数的值)定义。不考虑返回类型,也不参与(无论好坏)重载决议。

      因此,您尝试构建的功能根本没有意义。

      现在,您有两种选择:

      • 要求所有参数都继承或转换为通用类型,并返回该类型(这允许您提出非模板函数)
      • 采用模板并要求您的用户专门提供他们希望使用的类型的索引

      我不(也不能)在您的特定情况下哪种替代方案更可取,这是您必须做出的设计选择。

      最后,我要说的是你的推理水平可能太低了。您的用户真的需要独立访问每个字段吗?如果他们不这样做,您可以提供工具来依次将函数(访问者?)应用于每个元素。

      【讨论】:

        【解决方案4】:

        我认为 Xeo 有执行此操作的代码。

        这是我的尝试,有点奏效。问题是[] 不是参考。

        template<typename T, std::size_t N = std::tuple_size<T>::value - 1>
        struct foo {
          static inline auto bar(std::size_t n, const T& list)
                  -> decltype(((n != N) ? foo<T, N-1>::bar(n, list) : std::get<N>(list))) {
              return ((n != N) ? foo<T, N-1>::bar(n, list) : std::get<N>(list));
          }
        };
        
        template<typename T>
        struct foo<T, 0> {
          static inline auto bar(std::size_t n, const T& list)
                  -> decltype(std::get<0>(list)) {
              return std::get<0>(list);
          }
        };
        
        template <typename... Fields>
        class Record {
          private:
            std::tuple<Fields...> list;
        
          public:
            Record() {
              std::get<0>(list) = 5;
            }
        
            inline auto operator[](std::size_t n) 
                    -> decltype(foo<decltype(list)>::bar(n, list)) {
                    return foo<decltype(list)>::bar(n, list);
            }
        };
        
        int main() {
            Record<int, double> r;
            std::cout << r[0];
            return 0;
        }
        

        【讨论】:

        • 真的很好。我尝试修改您的代码以使用 get 周围的 std::ref() 和返回类型 decltype() &amp; 但到目前为止我没有管理。
        • @JohanLundberg 是的,当 Xeo 上线时,他应该有正确的代码。至少我认为是 Xeo 拥有它。
        • 哇,这段代码还能用吗?您不能在编译时使用运行时值。
        【解决方案5】:

        由于n是一个模板参数,它在编译时应该是已知的,但你想在运行时将它作为参数传递。

        此外,gcc 4.5.2 也因此而感到不高兴:

        g++ 1.cpp -std=c++0x
        1.cpp: In member function 'decltype (get<1u>(((Record<Fields>*)0)->Record<Fields>::list)) Record<Fields>::operator[](size_t)':
        1.cpp:14:25: error: 'n' cannot appear in a constant-expression
        

        【讨论】:

          【解决方案6】:

          如果您对编译时常量感到满意,并且仍然希望使用漂亮的 operator[] 语法,那么这是一个有趣的解决方法:

          #include <tuple>
          
          template<unsigned I>
          struct static_index{
            static unsigned const value = I;
          };
          
          template <typename... Fields>
          class Record {
            private:
              typedef std::tuple<Fields...> tuple_t;
              tuple_t list;
          
            public:
              Record() {}
          
              template<unsigned I>
              auto operator[](static_index<I>)
                  -> typename std::tuple_element<
                         I, tuple_t>::type&
              {
                  return std::get<I>(list);
              }
          };
          
          namespace idx{
          const static_index<0> _0 = {};
          const static_index<1> _1 = {};
          const static_index<2> _2 = {};
          const static_index<3> _3 = {};
          const static_index<4> _4 = {};
          }
          
          int main() {
              Record<int, double> r;
              r[idx::_0];
              return 0;
          }
          

          Live example on Ideone. 虽然我个人只是建议这样做:

          // member template
          template<unsigned I>
          auto get()
              -> typename std::tuple_element<
                     I, tuple_t>::type&
          {
              return std::get<I>(list);
          }
          
          // free function
          template<unsigned I, class... Fields>
          auto get(Record<Fields...>& r)
            -> decltype(r.template get<I>())
          {
            return r.template get<I>();
          }
          

          Live example on Ideone.

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-22
            • 1970-01-01
            • 2015-11-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多