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