如果参数具有成员变量,则专用化函数

Specialize function if argument has member variable

本文关键字:专用 函数 变量 参数 成员 如果      更新时间:2023-10-16

我有一个模板化的错误报告函数,因为它可以报告许多不同邮件类的错误:

template <typename MSG>
void reportErr(const MSG& msg)
{
std::cout << "ERROR: " << msg.error << std::endl;
}

但是,某些类型的消息具有可以报告的详细错误或其他专门的错误报告,例如

template<>
void reportErr(const SpecificMsg& msg)
{
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}

由于有许多类型,例如SpecificMsg,我宁愿不为每种类型创建单独的模板专用化。是否可以为具有.details成员变量的任何类型的类型创建泛型专用化/部分专用化?

如果可能的话,我想要一种方法来做这件事(所以如果它有.details,一个专业化,如果它有.other_info,另一个,等等(。

编辑:这是明确询问函数。我见过执行类似操作以专门化模板的代码,但我从未遇到过对非成员函数执行我想要的操作的代码。我怀疑将用于类的方法转换为适用于函数并不难,但我一直无法弄清楚如何做到这一点。

编辑 2:我的 gcc 版本 (4.6.3( 似乎不支持完整的 C++11 标准,因此"重复"问题中提到的void_t选项对我不起作用。我的编译器抱怨"在'类型'之前有预期的嵌套名称说明符"等,甚至不让我定义void_t。因此,我从我的问题中删除了 C++11 标签。

我会为此使用SFINAE。首先,让我们定义两个函数,它们为消息返回错误字符串:

namespace detail
{
// for messages with "details" member:
template<typename MsgType>
std::string makeMsgString(const MsgType& msg, decltype(MsgType::details)*)
{
return "Error: " + msg.error + ", details: " + msg.details;
}
// for messages without "details" member:
template<typename MsgType>
std::string makeMsgString(const MsgType& msg, ...)
{
return "Error: " + msg.error + ", no details";
}
}

现在,这些函数可以像这样使用:

struct NonSpecificMsg { std::string error; };
struct SpecificMsg { std::string error, details; };
template<typename MsgType>
void reportErr(const MsgType& msg)
{
std::cout << detail::makeMsgString(msg, nullptr) << "n";
}
int main()
{
reportErr(NonSpecificMsg { "some error" }); // 1
reportErr(SpecificMsg { "some other error", "some details" }); // 2
return 0;
}

这里发生了什么?

调用 1(:NonSpecificMsg没有details成员,因此不存在第一个重载。由于MsgType::details不存在,因此decltype(MsgType::details)*不是有效的类型。SFINAE 会导致忽略此定义,而不是在编译期间引发错误。 只有重载 2(,它不访问details成员。

调用 2(:SpecificMsgdetails,因此编译器会考虑这两个重载。但是,可变参数函数重载(第二个(的优先级始终低于任何其他匹配重载,因此选择第一个。

编辑:这是一个C++11解决方案。不幸的是,decltype是在GCC 4.8中引入的。

编辑2:事实证明,decltype可以与GCC 4.6一起使用(它是在4.3版本中引入的(。版本 4.8.1 更改了其语义,但在 OP 的情况下,以前的版本将起作用 - 请参阅 GCC 的C++状态页面

如果

可能的话,我想要一种方法来做这件事(所以如果它有 .details,则一个专业化,如果它有 .other_info,则另一个专业化,等等(。

如果我得到了您的期望,您可以将choice-trick 与decltype结合使用,如以下示例所示:

#include <iostream>
template<int N>
struct choice: choice<N-1> {};
template<>
struct choice<0> {};
struct Foo { int error; };
struct Bar { int error; int details; };
struct Quux { int error; char other_info; };
template<typename MSG>
auto reportErr(choice<2>, const MSG& msg) -> decltype(msg.details, void()) {
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}
template<typename MSG>
auto reportErr(choice<1>, const MSG& msg) -> decltype(msg.other_info, void()) {
std::cout << "ERROR: " << msg.error;
std::cout << ", other_info: " << msg.other_info << std::endl;
}
template <typename MSG>
void reportErr(choice<0>, const MSG& msg) {
std::cout << "ERROR: " << msg.error << std::endl;
}
template <typename MSG>
void reportErr(const MSG &msg) {
reportErr(choice<100>{}, msg);
}
int main() {
reportErr(Foo{0});
reportErr(Bar{0, 42});
reportErr(Quux{0, 'c'});
}

查看它在wandbox上运行(实际上使用 GCC 4.5.4,您提到的版本不可用(。它利用重载分辨率根据消息的类型选取函数的工作版本,并丢弃介于两者之间的所有内容。您可以添加更多专业化(让我们这样称呼它们,即使它们毕竟不是正确的专业化(,并通过根据需要调整choice参数来根据您的偏好对它们进行排序(其值越高,专业化的优先级越高(。

通过将

choice技巧与基于 SFINAE 的解决方案中的sizeof相结合,也可以完成类似的事情,类似于我上面显示的。
特别是,这里有一个工作示例:

#include <iostream>
template<int N>
struct choice: choice<N-1> {};
template<>
struct choice<0> {};
struct Foo { int error; };
struct Bar { int error; int details; };
struct Quux { int error; char other_info; };
template<typename MSG, std::size_t = sizeof(MSG::details)>
void reportErr(choice<2>, const MSG& msg) {
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}
template<typename MSG, std::size_t = sizeof(MSG::other_info)>
void reportErr(choice<1>, const MSG& msg) {
std::cout << "ERROR: " << msg.error;
std::cout << ", other_info: " << msg.other_info << std::endl;
}
template <typename MSG>
void reportErr(choice<0>, const MSG& msg) {
std::cout << "ERROR: " << msg.error << std::endl;
}
template <typename MSG>
void reportErr(const MSG &msg) {
reportErr(choice<100>{}, msg);
}
int main() {
reportErr(Foo{0});
reportErr(Bar{0, 42});
reportErr(Quux{0, 'c'});
}

看到它启动并运行在wandbox.优点是此解决方案不会受到您在前一个解决方案中收到的烦人警告的影响。


我用比你要求的(GCC 4.5.4(更旧的编译器测试了它,所以我非常有信心它们也适用于GCC 4.6.x。

注意:这是在 OP 指定其 gcc/c++ 版本之前编写的 C++17 答案。我让它在那里希望能帮助别人

您可以标记消息类型并在编译时测试这些标记:

#include <iostream>
#include <type_traits>
#include <string>
struct HasErrorMember { std::string error = "error"; };
struct HasDetailsMember { std::string details = "details"; };
template<class MSG>
void reportErr(const MSG& msg)
{
if constexpr (std::is_base_of_v<HasErrorMember, MSG>)   std::cout << "ERROR: " << msg.error;
if constexpr (std::is_base_of_v<HasDetailsMember, MSG>) std::cout << ", details: " << msg.details;
std::cout << "n";
}
struct MsgSimple : HasErrorMember
{};
struct MsgDetails : HasErrorMember, HasDetailsMember
{};
int main()
{
MsgSimple  ms;
MsgDetails md;
std::cout << "error only:n";
reportErr(ms);
std::cout << "error + details:n";
reportErr(md);
}

根据您的需求,这些标签可以嵌入成员本身,也可以是空的,将确保成员><标签一致性的责任交给开发人员。>

现场演示

只有 C++03,特征比 C++11 更冗长(如std::is_detected(,您可以执行以下操作:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               
template <typename U>                                                   
class traitsName                                                        
{                                                                       
private:                                                                
template<typename T, T> struct helper;                              
template<typename T>                                                
static char check(helper<signature, funcName>*);                    
template<typename T> static int check(...);                         
public:                                                                 
static                                                              
const bool value = sizeof(check<U>(0)) == sizeof(char);             
}

然后

// Would be in std in C++11
template <bool, typename T = void> struct enable_if
{
typedef T type;
};
template <typename T> struct enable_if<false, T>
{
};

然后

DEFINE_HAS_SIGNATURE(has_details, &T::details, std::string (T::*));
template <typename MSG>
typename enable_if<!has_details<MSG>>::type
reportErr(const MSG& msg)
{
std::cout << "ERROR: " << msg.error << std::endl;
}
template <typename MSG>
typename enable_if<has_details<MSG>>::type
void reportErr(const MSG& msg)
{
std::cout << "ERROR: " << msg.error;
std::cout << ", details: " << msg.details << std::endl;
}

演示