Специализированная функция, если аргумент имеет переменную-член

У меня есть функция для отчетов об ошибках, которая основана на шаблонах, потому что она может сообщать об ошибках для многих различных классов сообщений:

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 из своего вопроса.

4 ответа

Решение

Если возможно, я бы хотел сделать это в целом (одна специализация, если у него есть.details, другая, если у него есть.other_info и т. Д.).

Если я получил ваши ожидания, вы можете использовать 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, упомянутая вами версия недоступна). Он использует разрешение перегрузки для выбора рабочей версии функции в соответствии с типом сообщения и отбрасывает все, что находится между ними. Вы можете добавить больше специализаций (назовем их так, хотя они все-таки не являются должными специализациями) и отсортировать их в соответствии с вашими предпочтениями, настроив choice параметр по мере необходимости (чем выше его значение, тем выше приоритет специализации).


Нечто подобное можно также сделать, комбинируя choice трюк с 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.

Я бы использовал SFINAE для этого. Во-первых, давайте определим две функции, которые возвращают error строку для сообщения:

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 предыдущие версии будут работать - см. Страницу состояния C++ в GCC

Примечание. Это ответ C++17, написанный до того, как OP определил их версию gcc/ C++. Я позволил это там, чтобы, надеюсь, помочь другим.

Вы можете пометить типы сообщений и проверить их во время компиляции:

#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;
}

демонстрация

Другие вопросы по тегам