Идиома к чистой пересылке

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

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 или ничего не так как кажется?

0

Решение

Я бы не использовал макросы.

Есть ситуации, когда макросы являются единственной возможностью, но я осмелюсь сказать, что это не так. Макросы выполняют простую обработку текста; то, что они обрабатывают, не является частью метамодели 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;
};
1

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

Других решений пока нет …

По вопросам рекламы [email protected]