如果参数有成员变量,则特化函数
Specialize function if argument has member variable
我有一个模板化的错误报告功能,因为它可以报告许多不同消息的错误类:
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
成员变量的任何类型创建通用 specialization/partial 特化?
如果可能的话,我想要一种通用的方法(所以如果它有 .details
,那么一个专业化,如果它有 .other_info
,就不同,等等)。
编辑:这是明确询问函数。我见过代码做类似的事情来专门化模板 类,但我从来没有遇到过能为非成员函数做我想做的事情。我怀疑将用于 类 的方法转换为适用于函数并不难,但我一直无法弄清楚如何去做。
编辑 2:我的 gcc (4.6.3) 版本似乎不支持完整的 C++11 标准,因此 void_t
中提到的选项"duplicate" 问题不适合我。我的编译器抱怨 "expected nested-name-specifier before 'type'" 等等,甚至不让我定义 void_t。因此,我从我的问题中删除了 C++11 标签。
注意:这是在 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);
}
根据您的需要,这些标签可以自己嵌入成员或可以为空,将确保成员<->标签一致性的责任交给开发人员。
我会为此使用 SFINAE。首先,让我们定义两个函数,其中return错误消息的字符串:
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): SpecificMsg
有 details
,因此编译器会考虑这两个重载。但是,可变函数重载(第二个)的优先级始终低于任何其他匹配重载,因此选择第一个。
编辑:这是一个 C++11 解决方案。不幸的是,decltype
是在 GCC 4.8 中引入的。
编辑2:原来decltype
可以与GCC 4.6一起使用(它是在4.3版中引入的).版本 4.8.1 改变了它的语义,但在 OP 的情况下,以前的版本可以工作 - 参见 GCC's C++ status page
If possible, I'd like a way to do this generally (so one specialization if it has .details, a different one if it has .other_info, etc).
如果我如您所愿,您可以使用 choice
技巧结合 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,您提到的版本不可用)。它利用重载解析根据消息的类型选择函数的 工作版本 并丢弃其间的所有内容。您可以添加更多 specializations(让我们这样称呼它们,即使它们毕竟不是 specializations)并根据您的喜好通过调整对它们进行排序根据需要设置 choice
参数(其值越高,specialization 的优先级越高)。
在基于 SFINAE 的解决方案中,将 choice
技巧与 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.
仅使用 C++03,traits 比使用 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;
}
我有一个模板化的错误报告功能,因为它可以报告许多不同消息的错误类:
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
成员变量的任何类型创建通用 specialization/partial 特化?
如果可能的话,我想要一种通用的方法(所以如果它有 .details
,那么一个专业化,如果它有 .other_info
,就不同,等等)。
编辑:这是明确询问函数。我见过代码做类似的事情来专门化模板 类,但我从来没有遇到过能为非成员函数做我想做的事情。我怀疑将用于 类 的方法转换为适用于函数并不难,但我一直无法弄清楚如何去做。
编辑 2:我的 gcc (4.6.3) 版本似乎不支持完整的 C++11 标准,因此 void_t
中提到的选项"duplicate" 问题不适合我。我的编译器抱怨 "expected nested-name-specifier before 'type'" 等等,甚至不让我定义 void_t。因此,我从我的问题中删除了 C++11 标签。
注意:这是在 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);
}
根据您的需要,这些标签可以自己嵌入成员或可以为空,将确保成员<->标签一致性的责任交给开发人员。
我会为此使用 SFINAE。首先,让我们定义两个函数,其中return错误消息的字符串:
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): SpecificMsg
有 details
,因此编译器会考虑这两个重载。但是,可变函数重载(第二个)的优先级始终低于任何其他匹配重载,因此选择第一个。
编辑:这是一个 C++11 解决方案。不幸的是,decltype
是在 GCC 4.8 中引入的。
编辑2:原来decltype
可以与GCC 4.6一起使用(它是在4.3版中引入的).版本 4.8.1 改变了它的语义,但在 OP 的情况下,以前的版本可以工作 - 参见 GCC's C++ status page
If possible, I'd like a way to do this generally (so one specialization if it has .details, a different one if it has .other_info, etc).
如果我如您所愿,您可以使用 choice
技巧结合 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,您提到的版本不可用)。它利用重载解析根据消息的类型选择函数的 工作版本 并丢弃其间的所有内容。您可以添加更多 specializations(让我们这样称呼它们,即使它们毕竟不是 specializations)并根据您的喜好通过调整对它们进行排序根据需要设置 choice
参数(其值越高,specialization 的优先级越高)。
在基于 SFINAE 的解决方案中,将 choice
技巧与 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.
仅使用 C++03,traits 比使用 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;
}