3

我有一个模板化的错误报告功能,因为它可以报告许多不同消息类的错误:

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“重复”问题中提到的选项对我不起作用。我的编译器抱怨“'type'之前的预期嵌套名称说明符”等,甚至不让我定义void_t。因此,我从我的问题中删除了 C++11 标签。

4

4 回答 4

3

如果可能的话,我想要一种一般的方法(如果它有.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-trick 与sizeof基于 SFINAE 的解决方案相结合来完成,类似于我上面显示的。
特别是,这是一个工作示例:

#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 一起使用。

于 2018-05-21T12:37:00.150 回答
3

我会为此使用 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): SpecificMsghas details,因此编译器会考虑这两个重载。但是,可变参数函数重载(第二个)的优先级总是低于任何其他匹配的重载,因此选择第一个。

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

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

于 2018-05-18T21:20:02.947 回答
1

注意:这是在 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);
}

根据您的需要,这些标签可以自己嵌入成员,也可以为空,将确保成员<->标签一致性的责任交给开发人员。

现场演示

于 2018-05-11T15:43:22.500 回答
0

只有 C++03,traits 比 C++11 (as 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;
}

演示

于 2018-05-22T00:59:04.027 回答