Выбор конструктора шаблона Variadic завершается неудачно, когда аргумент является ссылкой
У меня есть следующий код:
#include <iostream>
#include <typeinfo>
template <typename T>
struct A : T {
template <typename ...Args>
A(Args&&... params) : T(std::forward<Args>(params)...), x(0) {
std::cout << "Member 'x' was default constructed\n";
}
template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
A(O o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
}
int x;
};
struct B{
B(const char*) {}
};
int main() {
A<B> a("test");
A<B> y(3, "test");
return 0;
}
Работает нормально и печатает
Member 'x' was default constructed
Member 'x' was constructed from arguments
Однако, если первый аргумент второй перегрузки является ссылкой, внезапно вторая перегрузка никогда не берется, и компиляция завершается неудачно:
template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
A(O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
} // Note the O& in the arguments
Почему это? Можно ли это исправить и избежать копий?
РЕДАКТИРОВАТЬ: Использование универсальной ссылки, очевидно, заставляет его работать снова. const
ссылка, которую я бы на самом деле хотел, тоже не работает.
Кроме того, даже сохранение входного параметра в отдельном значении (избегая значения r) все равно не будет работать:
int main() {
double x = 3.0;
A<B> y(x, "test"); // Still not working
return 0;
}
1 ответ
Почему это?
В случае следующей декларации:
template <typename O>
A(O& o);
вызов:
A{3};
выводит O
типа быть int
следовательно, вы получите следующую реализацию:
A(int& o);
Но то, что вы делаете, вы пытаетесь связать значение (которое 3
безусловно, есть) к этому конкретному неконстантному значению lvalue, и этонедопустимо.
Можно ли это исправить и избежать копий?
Вы можете объявитьo
введите также ссылку дляпересылки, а затем forward
это к конструкторуx
(но для примитивных типов, таких как int
это действительно вообще не нужно)
template <typename O>
A(O&& o) : x{std::forward<O>(o)} {}
В качестве альтернативы, вы можете объявить конструктор как принимающий ссылку на константное значение (так что она может быть связана с значениями):
template <typename O>
A(const O& o) : x{o} {}
Использование универсальной ссылки решает проблему, но, к сожалению, ссылка на const (а это именно то, что я хотел) не делает.Кроме того, даже сохранение входного параметра в отдельном значении (избегая значения r) все равно не будет работать.
Это связано с тем, что универсальная ссылка почти всегда дает точное совпадение, а первый конструктор, использующий универсальные ссылки, является лучшей жизнеспособной функцией в процедуре разрешения перегрузки.
При прохождении значения выводится int&&
лучше подходит для значений, чем const int&
,
При прохождении lvalue выводится int&
лучше всего подходит для неконстантных lvalues (например, вашей переменной x
) чем const int&
,
Сказав это, этот жадный конструктор, принимающий универсальные ссылки, является в обоих случаях лучшей жизнеспособной функцией, потому что при создании экземпляра:
template <typename... Args>
A(Args&&... params);
template <typename O, typename... Args>
A(const O& z, Args&&... params);
например, для следующего звонка:
double x = 3.0;
A a(x, "test");
компилятор заканчивается:
A(double&, const char (&)[5]);
A(const double&, const char (&)[5]);
где первая подпись лучше соответствует (не нужно добавлять const
квалификация).
Если по каким-то причинам вы действительно хотите иметь это O
тип для шаблонов (теперь независимо от того, будет ли это универсальная ссылка или ссылка константного значения), вы должны отключить первый жадный конструктор из процедуры разрешения перегрузки, если его первый аргумент можно использовать для построения int
(так же, как второй включен при таких условиях):
template <typename T>
struct A : T
{
template <typename Arg, typename... Args, typename = typename std::enable_if<!std::is_constructible<int, Arg>::value>::type>
A(Arg&& param, Args&&... params) : T(std::forward<Arg>(param), std::forward<Args>(params)...), x(0) {
std::cout << "Member 'x' was default constructed\n";
}
template <typename O, typename... Args, typename = typename std::enable_if<std::is_constructible<int, O>::value>::type>
A(const O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
}
int x;
};