Упрощение конструктора из C++17
У меня есть некоторый класс A и следующее определение конструктора класса B:
template<typename T, std::enable_if_t<std::is_convertible_v<std::remove_cvref_t<T>, A>, int> = 42>
B(T&& a) : a{std::forward<T>(a)} {}
Как я могу переписать это проще в C++20?
Достаточно ли следующего варианта?
template<std::convertible_to<A> T>
B(T&& a) : a{std::forward<T>(a)} {}
Или надо что-то такое?
template<typename T>
requires std::convertible_to<std::remove_cvref_t<T>, A>
B(T&& a) : a{std::forward<T>(a)} {}
Нужен ли std::remove_cvref_t<T> в C++17 варианте?
Ответы (1 шт):
remove_cvref_t лишний в обоих случаях. Вот пример, где он только мешает:
#include <type_traits>
#include <utility>
struct A
{
A(int &) {}
};
struct B
{
A a;
// Работает:
// template <typename T, std::enable_if_t<std::is_convertible_v<T,A>, int> = 42>
// Не работает:
template <typename T, std::enable_if_t<std::is_convertible_v<std::remove_cvref_t<T>,A>, int> = 42>
B(T&& a) : a{std::forward<T>(a)} {}
};
int main()
{
int x;
B b(x); // error: no matching constructor for initialization of 'B'
}
Поэтому:
Достаточно ли следующего варианта?
template <std::convertible_to<A> T> B(T &&a) : a{std::forward<T>(a)} {}
Да, достаточно.
Еще, меня смущает a{...} вместо a(...). Пример:
#include <type_traits>
#include <utility>
struct A
{
A(int) {}
};
struct B
{
A a;
template <typename T, std::enable_if_t<std::is_convertible_v<T, A>, int> = 42>
B(T&& a) : a{std::forward<T>(a)} {}
};
int main()
{
B b((long long)42);
}
Здесь is_convertible_v возвращает true (convertible_to сделал бы то же самое), но a{...} не компилируется с ошибкой:
error: non-constant-expression cannot be narrowed from type 'long long' to 'int' in
initializer list
Еще, стоит использовать is_constructible/constructible_from вместо is_convertible/convertible_to. Последний требует, чтобы существовало неявное преобразование, но вам достаточно явного.
Пример, когда это создает проблему:
#include <type_traits>
#include <utility>
struct A
{
explicit A(int) {}
};
struct B
{
A a;
template <typename T, std::enable_if_t<std::is_convertible_v<T, A>, int> = 42>
B(T&& a) : a{std::forward<T>(a)} {}
};
int main()
{
B b(42); // error: no matching constructor for initialization of 'B'
}
И тогда еще по-хорошему стоит навесить условный explicit (так делает конструктор std::optional, например), вот так:
template <typename T>
requires std::constructible_from<A, T>
explicit(!std::convertible_to<T, A>)
B(T &&a) : a(std::forward<T>(a)) {}