如何使用通用模板函数来处理具有不同成员的对象?

时间:2017-01-30 21:10:51

标签: c++ c++11 templates

我已经花了一段时间寻找解决方案,但是,我可能不知道我想要完成的确切定义或语言语法,所以我决定发布。

我有一些像这样的对象/结构:

struct A
{
  char myChar;
  bool hasArray = false;
};

template <uint8_t ARRAY_LEN>
struct AA : public A
{
  hasArray = true;
  uint8_t myArray[ARRAY_LEN];
};

我想创建一个泛型函数,它既可以接受这两种对象类型,也可以执行常规工作以及派生struct AA的特定工作。如下所示:

template <typename T>
void func(T (&m)) 
{
  if (T.hasArray)
  {
    // do some processing with m.myArray
    std::cout << sizeof(m.myArray) << std::endl;
    // ...
  }
  // common processing
  std::cout << "myChar: " << m.myChar << std::endl;
};

我希望能够像这样调用函数:

A a;
AA aa;
func(a);   // compiler error, this would not work as no array member
func(aa);  // this works

当然,这只是一个说明我意图的例子,但总结了我想做的事情。实际代码更复杂,涉及更多对象。我知道我可以超载,但我想知道是否有办法用一个通用函数做到这一点?另请注意,我理解为什么编译器抱怨我想知道的示例代码是否存在我缺少的变通方法或其他一些c ++功能。我不想做任何类型的铸造...... - 使用c ++ 11和GCC 4.8.5

3 个答案:

答案 0 :(得分:2)

这是一个复杂程度相当大的C ++ 14特性。 C ++ 17引入了if constexpr来简化这一过程;但它是可行的。

template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

constexpr inline index_t<0> dispatch_index() { return {}; }
template<class B0, class...Bs,
  std::enable_if_t<B0::value, int> =0
>
constexpr index_t<0> dispatch_index( B0, Bs... ) { return {}; }
template<class B0, class...Bs,
  std::enable_if_t<!B0::value, int> =0
>
constexpr auto dispatch_index( B0, Bs... ) { 
  return index< 1 + dispatch_index( decltype(Bs){}...) >;
}

template<class...Bs>
auto dispatch( Bs... ) {
  using I = decltype(dispatch_index( decltype(Bs){}... ));
  return [](auto&&...args)->decltype(auto){
    return std::get<I::value>( std::make_tuple(decltype(args)(args)..., [](auto&&...){}) );
  };
}

dispatch( some_test )返回一个带auto&&...的lambda。如果some_test是类似true的类型,它反过来返回第一个参数,如果[](auto&&...){}是假的,那么它返回第二个参数(如果没有第二个参数则返回some_test)型。

然后我们编写代码来检测您的myArray

namespace details {
  template<template<class...>class Z, class=void, class...Ts>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;

template<class T>
using myArray_type = decltype( std::declval<T>().myArray );

template<class T>
using has_myArray = can_apply< myArray_type, T >;
如果has_myArray<T>有成员T,则

.myArray为真。

我们将这些联系在一起

dispatch( has_myArray<T>{} )(
  [&](auto&& m) {
    // do some processing with m.myArray
    std::cout << sizeof(m.myArray) << std::endl;
    // ...
  }
)( m );

现在,当且仅当m.myArray有效时,才会运行中间的lambda。

可以编写更复杂的测试来检查存在而不仅仅是存在,但上述情况通常就足够了。

在像MSVC 2015这样的非C ++ 11编译器中,替换

std::enable_if_t<B0::value, int> =0

std::enable_if_t<!B0::value, int> =0

class = std::enable_if_t<B0::value>

class = std::enable_if_t<!B0::value>, class=void

分别。是的,这些都是丑陋的。与MSVC编译团队联系。

如果您的编译器缺少C ++ 14,则必须编写自己的void_t并编写自己的enable_if_t或使用enable_if使用丑陋的较长版本。

此外,模板变量index在C ++ 11中是非法的。将index<blah>替换为index_t<blah>{}

缺乏auto&& lambdas使上述非常痛苦;您可能必须将lambda转换为外联函数对象。但是,自动lambda是人们实现的第一个C ++ 14特性之一,通常在它们完成C ++ 11之前。

以上代码设计合理,但可能包含拼写错误。

答案 1 :(得分:1)

  

有一种方法可以使用一个通用函数吗?

我不这么认为,因为如果您在此功能中插入sizeof(m.myArray),则无法使用没有myArray成员的类型调用它。即使它是代码的一部分,运行时,也没有执行,因为编译器需要编译它。

但是,如果我理解正确,您的hasArray会说您的结构是否有myArray成员。所以我想你可以在static constexpr成员中对其进行转换,如下所示

struct A
 {
   static constexpr bool hasArray { false };

   char myChar { 'z' };
 };

template <uint8_t ARRAY_LEN>
struct AA : public A
 {
   static constexpr bool hasArray { true };

   uint8_t myArray[ARRAY_LEN];
 };

现在,在func()中,您可以调用第二个函数func2()来选择两种情况:myArray或不myArray。您可以使用SFINAE,但在这种情况下(IMHO)是更好的标签分派。因此,您可以使用其他类型转换hasArray

template <typename T>
void func2 (T const & m, std::true_type const &)
 { std::cout << sizeof(m.myArray) << ", "; }

template <typename T>
void func2 (T const &, std::false_type const &)
 { }

template <typename T>
void func(T (&m)) 
 {
   func2(m, std::integral_constant<bool, T::hasArray>{});

   // common processing
   std::cout << "myChar: " << m.myChar << std::endl;
 }

现在您可以使用这两种类型调用func()

int main()
 {
   A       a;
   AA<12U> aa;

   func(a);  // print myChar: z
   func(aa); // print 12, myChar: z
 }

请务必加入type_traitsiostream

答案 2 :(得分:1)

如果您不想修改实例,重载在您的情况下运行正常:

#include<iostream>
#include<cstdint>

struct A
{
    char myChar;
};

template <uint8_t ARRAY_LEN>
struct AA : public A
{
    uint8_t myArray[ARRAY_LEN];
};

void func(const A &m)
{
    std::cout << "myChar: " << m.myChar << std::endl;
};

template <uint8_t AL>
void func(const AA<AL> &m) 
{
    std::cout << sizeof(m.myArray) << std::endl;
    func(static_cast<const A &>(m));
}

int main() {
    func(A{});
    func(AA<1>{});
}

如果您仍想使用模板功能和一些sfinae,我可能会使用类似的东西:

#include<iostream>
#include<cstdint>

struct A
{
    char myChar;
};

template <uint8_t ARRAY_LEN>
struct AA : public A
{
    uint8_t myArray[ARRAY_LEN];
};

void func(A &m)
{
    std::cout << "myChar: " << m.myChar << std::endl;
}

template <typename T>
auto func(T &m) -> decltype(m.myArray, void())
{
    std::cout << sizeof(m.myArray) << std::endl;
    A &a = m;
    func(a);
}

int main() {
    AA<1> aa{};
    A a{};
    func(a);
    func(aa);
}

请注意,在这两种情况下,您实际上并不需要hasArray成员数据。