Почему бы не вывести параметр шаблона из конструктора?
Мой вопрос сегодня довольно прост: почему компилятор не может вывести параметры шаблона из конструкторов классов, как это можно сделать из параметров функции? Например, почему следующий код не может быть действительным:
template<typename obj>
class Variable {
obj data;
public: Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); //would be equivalent to Variable<int> var(num),
return 0; //but actually a compile error
}
Как я уже сказал, я понимаю, что это неверно, поэтому мой вопрос: почему не так? Позволит ли это создать какие-либо серьезные синтаксические дыры? Есть ли случай, когда никто не хотел бы эту функциональность (где вывод типа может вызвать проблемы)? Я просто пытаюсь понять логику, позволяющую сделать вывод из шаблона для функций, но не для правильно сконструированных классов.
12 ответов
Я думаю, что это неверно, потому что конструктор не всегда является единственной точкой входа в класс (я говорю о конструкторе копирования и операторе =). Предположим, вы используете свой класс следующим образом:
MyClass m(string s);
MyClass *pm;
*pm = m;
Я не уверен, что парсеру было бы так очевидно знать, какой тип шаблона является MyClass pm;
Не уверен, что то, что я сказал, имеет смысл, но не стесняйтесь добавлять комментарии, это интересный вопрос.
C++ 17
Принято считать, что C++ 17 будет иметь вычитание типа из аргументов конструктора.
Примеры:
std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
В эпоху просвещения 2016 года, когда с момента появления этого вопроса у нас появилось два новых стандарта, а новый не за горами, важно знать, что компиляторы, поддерживающие стандарт C++17, будут компилировать ваш код как есть.,
Вывод аргумента шаблона для шаблонов классов в C++17
Здесь (любезно предоставлено редактором Олжаса Жумабека принятого ответа) документ с подробным описанием соответствующих изменений в стандарте.
Решение проблем из других ответов
Текущий самый лучший ответ
Этот ответ указывает на то, что "конструктор копирования и operator=
msgstr "не знаю правильных шаблонных специализаций.
Это нонсенс, потому что стандартный конструктор копирования и operator=
существует только для известного типа шаблона:
template <typename T>
class MyClass {
MyClass(const MyClass&) =default;
... etc...
};
// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm; // WHAT IS THIS?
*pm = m;
Здесь, как я отметил в комментариях, нет никаких причин для MyClass *pm
быть юридической декларацией с или без новой формы вывода: MyClass
не является типом (это шаблон), поэтому нет смысла объявлять указатель типа MyClass
, Вот один из возможных способов исправить пример:
MyClass m(string("blah blah blah"));
decltype(m) *pm; // uses type inference!
*pm = m;
Вот, pm
уже имеет правильный тип, и поэтому вывод является тривиальным. Более того, невозможно случайно смешать типы при вызове конструктора копирования:
MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));
Вот, pm
будет указатель на копию m
, Вот, MyClass
копируется из m
- который имеет тип MyClass<string>
(а не несуществующего типа MyClass
). Таким образом, в точке, где pm
тип выводится, есть достаточно информации, чтобы знать, что тип шаблона m
и, следовательно, тип шаблона pm
, является string
,
Более того, следующее всегда вызовет ошибку компиляции:
MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;
Это потому, что объявление конструктора копирования не является шаблонным:
MyClass(const MyClass&);
Здесь тип шаблона аргумента copy-constructor соответствует типу шаблона класса в целом; т.е. когда MyClass<string>
инстанцируется, MyClass<string>::MyClass(const MyClass<string>&);
создается с ним, и когда MyClass<int>
инстанцируется, MyClass<int>::MyClass(const MyClass<int>&);
создается экземпляр. Если он не указан явно или не объявлен шаблонизированный конструктор, у компилятора нет причин создавать экземпляр MyClass<int>::MyClass(const MyClass<string>&);
что, очевидно, было бы неуместно.
Ответ Кэтэлин Пити
Питиș приводит пример Variable<int>
а также Variable<double>
, тогда заявляет:
У меня одинаковое имя типа (переменная) в коде для двух разных типов (переменная и переменная). С моей субъективной точки зрения, это сильно влияет на читаемость кода.
Как отмечено в предыдущем примере, Variable
само по себе не является именем типа, хотя новая функция делает его похожим на синтаксис.
Затем Питий спрашивает, что произойдет, если не будет дан конструктор, который позволил бы сделать соответствующий вывод. Ответ заключается в том, что логический вывод не допускается, потому что логический вывод инициируется вызовом конструктора. Без вызова конструктора не будет никакого вывода.
Это похоже на вопрос о том, какая версия foo
выводится здесь:
template <typename T> foo();
foo();
Ответ заключается в том, что этот код является незаконным по указанной причине.
Ответ MSalter
Это, насколько я могу судить, единственный ответ, чтобы вызвать законную озабоченность по поводу предлагаемой функции.
Пример:
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?
Ключевой вопрос заключается в том, выбирает ли здесь компилятор конструктор с выводом типа или конструктор копирования?
Опробовав код, мы увидим, что выбран конструктор копирования. Чтобы расширить на примере:
Variable var(num); // infering ctor
Variable var2(var); // copy ctor
Variable var3(move(var)); // move ctor
// Variable var4(Variable(num)); // compiler error
Я не уверен, как предложение и новая версия стандарта определяют это; кажется, что это определяется "руководствами по выводам", которые представляют собой новый стандарт, который я еще не понимаю.
Я также не уверен, почему var4
удержание является незаконным; ошибка компилятора из g++, кажется, указывает на то, что оператор анализируется как объявление функции.
Вы не можете делать то, что просите, по причинам, к которым обращались другие люди, но вы можете сделать это:
template<typename T>
class Variable {
public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
return Variable<T>(instance);
}
что для всех намерений и целей это то же самое, что вы просите. Если вы любите инкапсуляцию, вы можете сделать make_variable статической функцией-членом. Это то, что люди называют по имени конструктор. Таким образом, он не только делает то, что вы хотите, но он почти называется тем, что вы хотите: компилятор выводит параметр шаблона из (именованного) конструктора.
NB: любой разумный компилятор оптимизирует временный объект, когда вы пишете что-то вроде
Variable<T> v = make_variable(instance);
Все еще отсутствует: это делает следующий код довольно неоднозначным:
int main()
{
int num = 2;
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
Предположим, что компилятор поддерживает то, что вы просили. Тогда этот код действителен:
Variable v1( 10); // Variable<int>
// Some code here
Variable v2( 20.4); // Variable<double>
Теперь у меня есть одно и то же имя типа (Variable) в коде для двух разных типов (Variable и Variable). С моей субъективной точки зрения, это сильно влияет на читаемость кода. Наличие одного и того же имени типа для двух разных типов в одном и том же пространстве имен мне кажется обманчивым.
Позднее обновление: еще одна вещь, которую следует учитывать: частичная (или полная) специализация шаблона.
Что если я специализируюсь на Variable и не предоставляю конструктора, как вы ожидаете?
Так что я бы:
template<>
class Variable<int>
{
// Provide default constructor only.
};
Тогда у меня есть код:
Variable v( 10);
Что должен делать компилятор? Используйте общее определение класса Variable, чтобы сделать вывод, что это Variable, а затем обнаружить, что Variable не предоставляет один конструктор параметров?
Стандарт C++03 и C++11 не позволяют вычитать аргументы шаблона из параметров, передаваемых в constuructor.
Но есть предложение "Вывод параметров шаблона для конструкторов", поэтому вы можете вскоре получить то, о чем просите. Изменить: действительно, эта функция была подтверждена для C++ 17.
См. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html и http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
Многие классы не зависят от параметров конструктора. Есть только несколько классов, которые имеют только один конструктор и параметризуются на основе типа (ов) этого конструктора.
Если вам действительно нужен вывод шаблона, используйте вспомогательную функцию:
template<typename obj>
class Variable
{
obj data;
public:
Variable(obj d)
: data(d)
{ }
};
template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
return Variable<obj>(d);
}
Вывод типов ограничен шаблонными функциями в текущем C++, но давно понято, что вывод типов в других контекстах был бы очень полезен. Следовательно, C++ 0x auto
,
Хотя именно то , что вы предлагаете, не будет возможно в C++ 0x, ниже показано, что вы можете быть довольно близко:
template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
// remove reference required for the case that x is an lvalue
return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}
void test()
{
auto v = MakeVariable(2); // v is of type Variable<int>
}
Вы правы, что компилятор может легко догадаться, но, насколько я знаю, он не входит в стандарт или C++0x, поэтому вам придется подождать еще как минимум 10 лет (стандартная норма ISO), прежде чем поставщики компиляторов добавят эту функцию.
Давайте рассмотрим проблему со ссылкой на класс, с которым каждый должен быть знаком - std::vector.
Во-первых, очень распространенным использованием вектора является использование конструктора, который не принимает параметров:
vector <int> v;
В этом случае очевидно, что никакой вывод не может быть выполнен.
Вторым распространенным применением является создание вектора с заданным размером:
vector <string> v(100);
Здесь, если были использованы выводы:
vector v(100);
мы получаем вектор целых, а не строк, и, вероятно, он не имеет размера!
Наконец, рассмотрим конструкторы, которые принимают несколько параметров - с "выводом":
vector v( 100, foobar() ); // foobar is some class
Какой параметр следует использовать для вывода? Нам нужен какой-то способ сообщить компилятору, что он должен быть вторым.
Учитывая все эти проблемы для класса, такого простого, как vector, легко понять, почему логический вывод не используется.
Делая ctor шаблоном, переменная может иметь только одну форму, но различные ctors:
class Variable {
obj data; // let the compiler guess
public:
template<typename obj>
Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); // Variable::data int?
float num2 = 2.0f;
Variable var2(num2); // Variable::data float?
return 0;
}
Увидеть? Мы не можем иметь несколько членов Variable::data.
Посмотрите C++ Шаблон Аргумент Аргумент для получения дополнительной информации об этом.