C++将非模板成员函数调用转发到模板函数

C++ Forward non-template member function call to template function

本文关键字:转发 函数 函数调用 成员 C++      更新时间:2023-10-16

我想在类"Record"中隐藏一个std::元组,并在其上提供一个运算符[]来访问元组的元素。不编译的天真代码是这样的:

#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[],就像调用数组一样。这可能吗?

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),或者我们可以构建一些相当好地隐藏它的东西,你的用户不会意识到他们的返回类型刚刚改变,隐式转换会对他们造成恶劣的影响。让这个明确的保险箱一些麻烦。

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

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

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

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

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

Record<Apple, Orange> fruitBasket;

然后我们会有:

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

这里没有什么事困扰你吗?

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

因此,您试图构建的函数根本没有意义。

现在,您有两种选择:

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

我不知道(也不能)在你的特定情况下,哪种选择更可取,这是你必须做出的设计选择。

最后,我要指出,你的推理水平可能太低了。您的用户真的需要独立访问每个字段吗?如果他们不这样做,你可以提供一些设施来依次将功能(访问者?)应用到每个元素。

我认为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;
}

由于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

如果您对编译时间常数很满意,但仍然希望使用漂亮的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;
}

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

Ideone上的实例。