Упрощение конструктора из 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 шт):

Автор решения: HolyBlackCat

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)) {}
→ Ссылка