template <typename U>
T add(U x) const {
if constexpr (std::is_same_v<T, std::vector<U>>) {
auto copy (val);
for (auto &n : copy) {
n += x;
}
return copy;
} else {
return val + x;
}
}
4. Теперь класс можно использовать. Посмотрим, насколько хорошо он может работать с разными типами, такими как int
, float
, std::vector<int>
и std::vector<string>
:
addable<int>{1}.add(2); // результат - 3
addable<float>{1.0}.add(2); // результат - 3.0
addable<std::string>{"aa"}.add("bb"); // результат - "aabb"
std::vector<int> v {1, 2, 3};
addable<std::vector<int>>{v}.add(10);
// is std::vector<int>{11, 12, 13}
std::vector<std::string> sv {"a", "b", "c"};
addable<std::vector<std::string>>{sv}.add(std::string{"z"});
// is {"az", "bz", "cz"}
Как это работает
Новая конструкция constexpr-if
работает точно так же, как и обычные конструкции if-else
. Разница между ними заключается в том, что значение условного выражения определяется во время компиляции. Весь код завершения, который компилятор сгенерирует из нашей программы, не будет содержать дополнительных ветвлений, относящихся к условиям constexpr-if
. Кто-то может сказать, что эти механизмы работают так же, как и макросы препроцессора #if
и #else
, предназначенные для подстановки текста, но в данном случае всему коду даже не нужно быть синтаксически правильным. Ветвления конструкции constexpr-if
должны быть синтаксически правильными, но неиспользованные ветви не обязаны быть семантически корректными.
Чтобы определить, должен ли код добавлять значение х
к вектору, задействуем типаж std::is_same
. Выражение std::is_same<A, B>::value
вычисляется в логическое значение true, если A и B имеют один и тот же тип. В нашем примере применяется условие std::is_same<T, std::vector<U>>::value
, которое имеет значение true
, если пользователь конкретизировал шаблон для класса T = std::vector<X>
и пробует вызвать функцию add
с параметром типа U = X
.
В одном блоке constexpr-if-else
может оказаться несколько условий (обратите внимание, что a
и b
должны зависеть от параметров шаблона, а не только от констант времени компиляции):
if constexpr (a) {
// что-нибудь делаем
} else if constexpr (b) {
// делаем что-нибудь еще
} else {
// делаем нечто совсем другое
}
С помощью C++17 гораздо легче как выразить, так и прочитать код, получающийся при метапрограммировании.
Дополнительная информация
Для того чтобы убедиться, каким прекрасным новшеством являются конструкции constexpr-if
для C++, взглянем, как решалась та же самая задача до С++17:
template <typename T>
class addable
{
T val;
public:
addable(T v) : val{v} {} template <typename U>
std::enable_if_t<!std::is_same<T, std::vector<U>>::value, T>
add(U x) const { return val + x; }
template <typename U>
std::enable_if_t<std::is_same<T, std::vector<U>>::value,
std::vector<U>>
add(U x) const {
auto copy (val);
for (auto &n : copy) {
n += x;
}
return copy;
}
};
Без конструкций constexpr-if
этот класс работает для всех необходимых нам типов, но кажется очень сложным. Как же он работает?
Сами реализации двух разных функций add
выглядят просто. Все усложняет объявление возвращаемого типа — выражение наподобие std::enable_if_t<условие, тип>
обращается в тип, если выполняется условие. В противном случае выражение std::enable_if_t
ни во что не обращается. Обычно такое положение дел считается ошибкой. Далее мы рассмотрим, почему в нашем случае это не так.
Для второй функции add
то же условие используется противоположным образом. Следовательно, условие может иметь значение true
только для одной из двух реализаций в любой момент времени.
Когда компилятор видит разные шаблонные функции с одинаковым именем и должен выбрать одну из них, в ход вступает важный принцип: он обозначается аббревиатурой SFINAE, которая расшифровывается как Substitution Failure is not an Error («Сбой при подстановке — не ошибка»). В данном случае это значит, что компилятор не генерирует ошибку, если возвращаемое значение одной из функций нельзя вывести на основе неверного шаблонного выражения (т.е. std::enable_if
, когда условие имеет значение false
). Он просто продолжит работу и попробует обработать другие реализации функции. Вот и весь секрет.