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

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

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

3

Решение

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

3

Другие решения

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

3

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

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

живое демо

1

Только с 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
По вопросам рекламы [email protected]