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

时间:2012-02-05 14:56:08

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

我想在我的类'Record'中隐藏一个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[]。这有可能吗?

6 个答案:

答案 0 :(得分:3)

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)或者我们可以构建一些能够合理地隐藏它的东西,你的用户不会意识到他们的返回类型只是改变了而隐式转换会对他们造成令人讨厌的事情。使这个明确的保险箱有些麻烦。

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

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

但让我们想象一下它是:

Record<Apple, Orange> fruitBasket;

然后我们会:

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

这里有什么东西不困扰你吗?

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

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

现在,您有两种选择:

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

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

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

答案 4 :(得分: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

答案 5 :(得分:0)

如果您对编译时常量很好并且仍希望使用漂亮的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.