Это просто еще один случай для void_t
.
Нам нужен небольшой вспомогательный шаблон Void
и укажите псевдоним типа шаблона удобства void_t
.
#include <type_traits>
template<typename...>
struct Void { using type = void; };
template<typename... T>
using void_t = typename Void<T...>::type;
Мы определяем основной шаблон, который реализует политику возврата.
template<typename T, typename = void>
struct Helper
{
static void
function(T& t)
{
std::cout << "doing something else with " << &t << std::endl;
}
};
и обеспечивает частичную специализацию для типов, которые поддерживают определенную операцию, в данном случае, .data().push_back(int)
.
template<typename T>
struct Helper<T, void_t<decltype(std::declval<T>().data().push_back(0))>>
{
static void
function(T& t)
{
std::cout << "pushing back data to " << &t << std::endl;
t.data().push_back(42);
}
};
Чтобы скрыть детали реализации Helper
от наших клиентов и позволить тип вычет для параметров шаблона, можно красиво обернуть его.
template<typename T>
void
function(T& t)
{
Helper<T>::function(t);
}
И так наши клиенты используют его.
#include <iostream>
#include <vector>
class Alpha
{
public:
std::vector<int>& data() { return this->data_; }
private:
std::vector<int> data_ {};
};
class Beta { /* has no data() */ };
int
main()
{
Alpha alpha {};
Beta beta {};
std::cout << "&alpha = " << &alpha << std::endl;
std::cout << "&beta = " << &beta << std::endl;
function(alpha);
function(beta);
}
Возможный выход:
&alpha = 0x7ffffd2a3eb0
&beta = 0x7ffffd2a3eaf
pushing back data to 0x7ffffd2a3eb0
doing something else with 0x7ffffd2a3eaf
Update: Как применить этот метод к нескольким членам
Методика показано выше, может быть применен к любому числу членов. Давайте составим небольшой пример. Скажем, мы хотим написать шаблонную функцию frobnicate
, которая принимает аргумент универсального типа и, если объект имеет ...
- ... функция
incr
элемента, который не принимает никаких аргументов, назвать его,
- ... членом
name
данных, приложите к нему текст, если это возможно, и
- ... элемент данных
numbers
, push_back
некоторые номера к нему, если это возможно.
Я действительно рекомендую вам решить эту проблему путем реализации три Helper struct
сек, как показано выше. Это не так много избыточного ввода и делает для более чистого кода.
Однако, если вы хотите проигнорировать этот совет, давайте посмотрим, как мы можем уменьшить типизацию с помощью макроса. Предполагая то же определение void_t
, как показано выше, мы можем определить следующий макрос.
#define MAKE_SFINAE_HELPER(NAME, TYPE, OPERATION, ARGS, CODE) \
template<typename TYPE, typename = void> \
struct NAME \
{ \
template<typename... AnyT> \
void \
operator()(AnyT&&...) noexcept \
{ \
/* do nothing */ \
} \
}; \
\
template<typename TYPE> \
struct NAME<TYPE, void_t<decltype(std::declval<TypeT>()OPERATION)>> \
{ \
void operator()ARGS noexcept(noexcept(CODE)) \
{ \
CODE; \
} \
};
Он будет определять struct
называется NAME
шаблонными от параметра типа TYPE
и определить первичный шаблон с оператором ()
, который принимает любое количество аргументов любого типа и абсолютно ничего не делает. Это используется как резерв, если желаемая операция не поддерживается.
Однако, если объект типа TYPE
поддерживает операцию OPERATION
, затем частичную специализацию с оператором ()
, который принимает параметры ARGS
и выполняет CODE
будет использоваться. Макрос определяется таким образом, что ARGS
может быть списком аргументов в скобках. К сожалению, грамматика препроцессора допускает только одно выражение, которое должно быть передано как CODE
. Это не большая проблема, так как мы всегда можем написать один вызов функции, который делегирует другую функцию. (Помните, что любая проблема в информатике может быть решена путем добавления дополнительного уровня косвенности - за исключением, конечно, проблемы слишком большого уровня косвенности ...) Оператор ()
частичной специализации будет объявлен noexcept
тогда и только тогда, когда CODE
есть. (Это также работает только потому, что CODE
ограничен одним выражением.)
Причина, по которой оператор ()
для основного шаблона является шаблоном, заключается в том, что в противном случае компилятор мог бы выпустить предупреждения о неиспользуемых переменных. Конечно, вы можете изменить макрос, чтобы принять дополнительный параметр FALLBACK_CODE
, который помещается в тело оператора основного шаблона ()
, который должен использовать тот же ARGS
.
В самых простых случаях, можно было бы объединить OPERATION
и параметр CODE
в один, а затем CODE
не может относиться к ARGS
, который эффективно ограничивает ARGS
к одному параметру типа TYPE
в этом случае вы могли бы избавиться от этот параметр также, если вам не нужна гибкость.
Итак, применим это к нашей проблеме. Во-первых, нам нужна вспомогательная функция для возврата чисел, потому что это не может быть написано (по крайней мере, давайте сделаем это) как одно выражение. Я делаю эту функцию как можно более общей, делая только предположения о имени члена.
template<typename ObjT, typename NumT>
void
do_with_numbers(ObjT& obj, NumT num1, NumT num2, NumT num3)
{
obj.numbers.push_back(num1);
obj.numbers.push_back(num2);
obj.numbers.push_back(num3);
}
Поскольку две другие желаемые операции легко могут быть записаны в виде одного выражения, нам не нужна дальнейшая косвенность для них. Итак, теперь мы можем создавать наши помощники SFINAE.
MAKE_SFINAE_HELPER(HelperIncr,
TypeT,
.incr(),
(TypeT& obj),
obj.incr())
MAKE_SFINAE_HELPER(HelperName,
TypeT,
.name += "",
(TypeT& obj, const std::string& appendix),
obj.name += appendix)
MAKE_SFINAE_HELPER(HelperNumbers,
TypeT,
.numbers.push_back(0),
(TypeT& obj, int i1, int i2, int i3),
do_with_numbers(obj, i1, i2, i3))
Оборудовано с этим, мы можем, наконец, написать нашу frobnicate
функцию. Это очень просто.
template<typename T>
void
frobnicate(T& object)
{
HelperIncr<T>()(object);
HelperName<T>()(object, "def");
HelperNumbers<T>()(object, 4, 5, 6);
}
Чтобы увидеть, что все работает, давайте сделаем два struct
сек, что частично поддерживают операции в вопросе.
#include <string>
#include <vector>
struct Widget
{
std::vector<int> numbers {1, 2, 3};
int counter {};
void incr() noexcept { this->counter += 1; }
};
struct Gadget
{
std::string name {"abc"};
int counter {};
void incr() noexcept { this->counter += 1; }
};
Так как я хочу, чтобы напечатать их, давайте также определить операторы <<
.
#include <iostream>
std::ostream&
operator<<(std::ostream& os, const Widget& w)
{
os << "Widget : { counter : " << w.counter << ", numbers : [";
int i {};
for (const auto& v : w.numbers)
os << (i++ ? ", " : "") << v;
os << "] }";
return os;
}
std::ostream&
operator<<(std::ostream& os, const Gadget& g)
{
os << "Gadget : { counter : " << g.counter << ", "
<< "name = \"" << g.name << "\" }";
return os;
}
И там мы идем:
int
main()
{
Widget widget {};
Gadget gadget {};
std::cout << widget << "\n" << gadget << "\n\n";
frobnicate(widget);
frobnicate(gadget);
std::cout << widget << "\n" << gadget << "\n";
}
Выход:
Widget : { counter : 0, numbers : [1, 2, 3] }
Gadget : { counter : 0, name = "abc" }
Widget : { counter : 1, numbers : [1, 2, 3, 4, 5, 6] }
Gadget : { counter : 1, name = "abcdef" }
Я рекомендую вам тщательно оценить затраты и выгоды от этого макроподхода. На мой взгляд, дополнительная сложность едва ли стоит небольших сбережений при наборе текста.
Это можно сделать с помощью шаблонов, см. Http://stackoverflow.com/a/257382/1111028 – scaryrawr
@scaryrawr Этот ответ не решает проблему. Я уже пробовал. Я смог создать шаблонное решение **, если ** член существует, но не смог заставить шаблоны работать, когда член не выходит. – sabbahillel