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

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

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 ]

0 голосов
/ 22 мая 2018

Только с 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;
}

Демо

0 голосов
/ 19 мая 2018

Я бы использовал 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;
}

Что здесь происходит?

Call 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

0 голосов
/ 21 мая 2018

Если возможно, я бы хотел сделать это в целом (одна специализация, если у нее есть .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.

0 голосов
/ 11 мая 2018

Примечание. Это ответ на 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);
}

В соответствии с вашими потребностями, эти теги могут встраиватьсясами члены или могут быть пустыми, возлагая ответственность за обеспечение согласованности тега <-> на разработчика.

live demo

...