Почему бы не определить параметр шаблона из конструктора? - PullRequest
98 голосов
/ 12 июня 2009

Мой вопрос сегодня довольно прост: почему компилятор не может вывести параметры шаблона из конструкторов классов, в отличие от параметров функции? Например, почему следующий код не может быть действительным:

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 ]

44 голосов
/ 12 июня 2009

Я думаю, что это недопустимо, потому что конструктор не всегда является единственной точкой входа в класс (я говорю о конструкторе копирования и операторе =). Предположим, вы используете свой класс следующим образом:

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);

Принимаемая бумага .

25 голосов
/ 28 июля 2011

Вы не можете делать то, что просите, по причинам, к которым обращались другие люди, но вы можете сделать это:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

что для всех намерений и целей это то же самое, что вы просите. Если вы любите инкапсуляцию, вы можете сделать make_variable статической функцией-членом. Это то, что люди называют по имени конструктор. Так что он не только делает то, что вы хотите, но и почти называется тем, что вы хотите: компилятор выводит параметр шаблона из (именованного) конструктора.

Примечание: любой разумный компилятор оптимизирует временный объект, когда вы напишите что-то вроде

Variable<T> v = make_variable(instance);
18 голосов
/ 22 августа 2016

В эпоху просвещения в 2016 году, когда с момента постановки этого вопроса у нас появилось два новых стандарта, а новый не за горами, важно знать, что компиляторы , поддерживающие стандарт C ++ 17, будут скомпилируйте ваш код как есть .

Вывод аргумента шаблона для шаблонов классов в C ++ 17

Здесь (любезно предоставлено редактором Олжаса Жумабека из принятого ответа) - документ, в котором подробно описаны соответствующие изменения в стандарте.

Решение проблем из других ответов

Текущий рейтинг ответа

Этот ответ указывает на то, что «конструктор копирования и operator=» не будет знать правильные специализации шаблона.

Это нонсенс, потому что стандартный конструктор копирования и 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;

Это потому, что объявление конструктора копирования не templated:

MyClass(const MyClass&);

Здесь тип шаблона аргумента копирующего конструктора соответствует типу шаблона класса в целом; то есть, когда создается экземпляр 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) в коде для двух разных типов (Variable и Variable). С моей субъективной точки зрения, это сильно влияет на читаемость кода.

Как отмечалось в предыдущем примере, Variable само по себе является , а не именем типа, хотя новая функция делает его синтаксически похожим на одно.

Затем Пити спрашивает, что произойдет, если не будет дан конструктор, который позволил бы сделать соответствующий вывод. Ответ заключается в том, что логический вывод не разрешен, потому что логический вывод инициируется вызовом конструктора . Без вызова конструктора нет логического вывода .

Это похоже на вопрос о том, какая версия foo выводится здесь:

template <typename T> foo();
foo();

Ответ заключается в том, что этот код является незаконным по указанной причине.

MSalter's answer

Это, насколько я могу судить, единственный ответ, чтобы вызвать законную озабоченность по поводу предлагаемой функции.

Пример:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

Ключевой вопрос заключается в том, выбирает ли здесь компилятор конструктор с типом или конструктор copy ?

Попробовав код, мы увидим, что выбран конструктор копирования. Расширить пример :

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

Я не уверен, как это указано в предложении и новой версии стандарта; кажется, что это определяется «руководствами по выводам», которые являются новым стандартом, которого я пока не понимаю.

Я также не уверен, почему вычет var4 является незаконным; ошибка компилятора в g ++, похоже, указывает на то, что оператор анализируется как объявление функции.

11 голосов
/ 12 июня 2009

Все еще отсутствует: это делает следующий код весьма двусмысленным:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
9 голосов
/ 12 июня 2009

Предположим, что компилятор поддерживает то, что вы просили. Тогда этот код действителен:

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 не предоставляет один конструктор параметров?

6 голосов
/ 11 апреля 2013

Стандарт C ++ 03 и C ++ 11 не позволяют вычитать аргументы шаблона из параметров, передаваемых в constuructor.

Но есть предложение "Вывод параметров шаблона для конструкторов", поэтому вы можете вскоре получить то, о чем просите. Редактировать: действительно, эта функция была подтверждена для C ++ 17.

См .: http://www.open -std.org / jtc1 / sc22 / wg21 / docs /apers / 2013 / n3602.html и http://www.open -std.org / jtc1 / sc22 / wg21 / документы / документы / 2015 / p0091r0.html

2 голосов
/ 12 июня 2009

Многие классы не зависят от параметров конструктора. Есть только несколько классов, которые имеют только один конструктор и параметризуются на основе типов этого конструктора.

Если вам действительно нужен вывод шаблона, используйте вспомогательную функцию:

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);
}
1 голос
/ 12 июня 2009

Удержание типов ограничено шаблонными функциями в текущем 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>
}
0 голосов
/ 12 июня 2009

Давайте посмотрим на проблему со ссылкой на класс, с которым каждый должен быть знаком - std :: vector.

Во-первых, очень распространенное использование вектора - это использование конструктора, который не принимает параметров:

vector <int> v;

В этом случае очевидно, что никакой вывод не может быть выполнен.

Вторым распространенным применением является создание вектора с заранее заданным размером:

vector <string> v(100);

Здесь, если использовался вывод:

vector v(100);

мы получаем вектор целых, а не строк, и, вероятно, он не имеет размера!

Наконец, рассмотрим конструкторы, которые принимают несколько параметров - с «логическим выводом»:

vector v( 100, foobar() );      // foobar is some class

Какой параметр следует использовать для вывода? Нам понадобится какой-то способ сообщить компилятору, что он должен быть вторым.

Учитывая все эти проблемы для класса, такого же простого, как вектор, легко понять, почему логический вывод не используется.

0 голосов
/ 12 июня 2009

См. Вывод аргумента шаблона C ++ для получения дополнительной информации об этом.

...