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

How to use generic template function to handle objects with different members?

本文关键字:成员 对象 处理 何使用 函数      更新时间:2023-10-16

我已经环顾四周寻找解决方案,但是,我可能不知道我想要完成的确切定义或语言语法,所以我决定发布。

我有一些对象/结构,如下所示:

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

这是一个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-like 类型,则返回第一个参数,如果some_test是 false-like 类型,则返回第二个参数(如果没有第二个参数,则返回[](auto&&...){})。

然后,我们编写代码来检测您的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 >;

如果T有一个成员.myArrayhas_myArray<T>是真的。

我们将这些连接在一起

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

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

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

在非 C++11 编译器(如 MSVC 2015)中,将

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&&lambda 使得上述工作非常痛苦;您可能必须将 lambda 转换为外联函数对象。 但是,人们在完成 C++11 之前实现的前 C++14 功能之一的自动 lambda。

上面的代码是可靠的设计,但可能包含拼写错误。

如果您不想修改实例,重载在您的情况下工作得很好:

#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成员数据。

一种方法可以用一个泛型函数来做到这一点吗?

我不这么认为,因为如果在此函数中插入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,但(恕我直言)在这种情况下是更好的标签调度。因此,您可以将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.