смешивание этот вопрос и это другой вопрос, Я пришел к следующему (на самом деле довольно простому) решению: идея заключается в том, чтобы сделать псевдоним типа доступным только в области действия реальной функции и проверить условия шаблона в подходящей точке:
template<typename... garbage>
struct firewall
{
typedef typename std::enable_if<sizeof...(garbage) == 0>::type type;
};
#define FIREWALL_CALL typename = typename firewall<garbage...>::type
#define TMPL_FIREWALL typename... garbage, FIREWALL_CALL
#define TMPL_ALIAS typename
#define TMPL_CONDITION(...) typename = \
typename std::enable_if<__VA_ARGS__::value>::type
С помощью этого кода мы можем добавить некоторые псевдонимы или условия, которые мы хотим, удобным способом. Этот код:
// Erase only qualifiers and references.
template<typename target>
struct pluck
{
typedef typename std::remove_cv<
typename std::remove_reference<target>::type>::type type;
}
// `some_fun` wants ensure its arguments are passed by non-constant values.
template<typename T>
typename pluck<T>::type
some_fun(typename pluck<T>::type a, typename pluck<T>::type b)
{
typename pluck<T>::type c;
// something
return c;
}
становится (только some_fun
):
template<typename T, TMPL_FIREWALL,
TMPL_ALIAS friendly = typename pluck<T>::type
TMPL_CONDITION(std::is_copy_constructible<friendly>)>
friendly some_fun(friendly a, friendly b)
{
friendly c;
// something
return c;
}
Цель firewall
, как @ipc Показанный во втором вопросе, который я поставил выше, — это принять любой аргумент, который может заменить псевдоним локального типа, определенный как аргумент шаблона по умолчанию.
А также это делает доступным полезный способ избежать другой более глубокой проблемы: когда вы хотите сделать функцию параметрической только для совершенной пересылки заранее известного типа; например, в следующей ситуации:
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename Str>
A(Str&& str) : _str(std::forward<Str>(str))
{}
B _b;
std::string _str;
};
Если вы хотите инициализировать _str
использование идеального механизма пересылки неизбежно превращается в неоднозначность с любым другим аргументом шаблона. Этого можно легко избежать с помощью следующего дополнительного макроса:
#define TMPL_PURE_FORWARDING(a, b) TMPL_FIREWALL, \
TMPL_CONDITION(std::is_same<typename _f_pluck<a>::type, b>)
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename fwStr, TMPL_PURE_FORWARDING(fwStr, std::string)>
A(fwStr&& str) : _str(std::forward<fwStr>(str))
{}
B _b;
std::string _str;
};
Если fwStr
не тип std::string
, std::string&
, std::string&&
или их постоянные версии, будет выбран другой конструктор, и, если нет другого конструктора, будет сгенерирована ошибка компилятора, говорящая, что std::enable_if<false, void>::type
не существует
Вопрос: в C ++ всегда предпочтительнее избегать использования макросов, но, как известно, шаблоны являются многословными, и эти ситуации (особенно вторая) очень распространены, или, по крайней мере, по моему опыту. Тогда эти макросы очень полезны.
Опасно ли использование макросов в этой ситуации? Это вообще хорошо или полезно idiom
или ничего не так как кажется?
Я бы не использовал макросы.
Есть ситуации, когда макросы являются единственной возможностью, но я осмелюсь сказать, что это не так. Макросы выполняют простую обработку текста; то, что они обрабатывают, не является частью метамодели C ++, но бессмысленными частями текста. Они небезопасны, их трудно понять, и их трудно поддерживать. Поэтому, если нет другого способа что-то сделать, лучше избегайте макросов.
Кроме того, ваш pluck<>
черта в основном делает то, что std::decay<>
делает. Это означает, что с простым псевдонимом шаблона, вы можете переписать some_fun
функция, облегчающая чтение а также разобрать (я заблудился, пытаясь собрать воедино все эти макросы).
#include <type_traits>
template<typename T>
using Decay = typename std::decay<T>::type;
template<typename T>
Decay<T> some_fun(Decay<T> a, Decay<T> b)
{
Decay<T> c;
// something
return c;
}
Точно так же для второго варианта использования вы можете написать что-то вроде:
template<typename T, typename U>
using CheckType = typename std::enable_if<
std::is_same<typename std::decay<T>::type, U>::value
>::type;
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename T, CheckType<T, std::string>* = nullptr>
A(T&& str) : _str(std::forward<T>(str))
{}
B _b;
std::string _str;
};
Других решений пока нет …