Чому б не зробити висновок параметра шаблону від конструктора?


102

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

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
}

Як я кажу, я розумію, що це невірно, тому моє питання: чому це не так? Чи дозволить це створити якісь основні синтаксичні дірки? Чи є приклад, коли не хотілося б цього функціоналу (де виведення типу спричиняло б проблеми)? Я просто намагаюся зрозуміти логіку, що дозволяє виводити шаблони для функцій, але не для належним чином побудованих класів.


Я б запросив когось (я це роблю, просто не зараз), щоб скласти відповідь Драгакара та Пітіса (принаймні) як хороші зустрічні приклади, чому це не може працювати
jpinto3912

2
Також зауважте, що це легко template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
подолати

3
Ви можете сортувати отримати те, що ви хочете var = Variable <decltype (n)> (n);
QuentinUK

18
C ++ 17 дозволить це! Ця пропозиція була прийнята: open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
підкреслити_29

1
@underscore_d Чудово! Про час! Мені здавалося природним, що саме так має працювати, і джерелом роздратування, яке не було.
amdn

Відповіді:


46

Я думаю, що це невірно, оскільки конструктор не завжди є єдиним пунктом вступу до класу (я говорю про конструктор копій та оператор =). Тож припустимо, що ви використовуєте свій клас так:

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

Прийнятий папір .


8
Це насправді чудовий момент, про який я ніколи не замислювався. Я не бачу жодного способу навколо того, що вказівник повинен був би бути типовим (тобто це повинен бути MyClass <string> * pm). Якщо це так, то все, що ви в кінцевому підсумку зробите, це вберегти себе від вказівки типу при створенні; кілька простих символів додаткової роботи (і лише якщо об’єкт зроблений на стеці, а не на купі, як зазначено вище). Я завжди підозрював, що умовивід класу може відкрити синтаксичну банку глистів, і я думаю, що це може бути все.
GRB

2
Я не дуже розумію, як дозволяти конфігурацію шаблону параметрів від конструкторів вимагати дозволу неспеціалізованих оголошень без викликів конструктора, як у вашому другому рядку. Тобто, MyClass *pmтут було б недійсно з тієї ж причини, що заявлену функцію template <typename T> void foo();не можна викликати без явної спеціалізації.
Кайл Странд

3
@KyleStrand Так, кажучи, що "аргументи шаблону класу не можна вивести з їх конструкторів, оскільки [приклад, який не використовує жодного конструктора] ", ця відповідь абсолютно не має значення. Я справді не можу повірити, що це було прийнято, досягнув +29, знадобилося 6 років, щоб хтось помітив кричущу проблему, і просидів без жодної голоси протягом 7 років. Хіба ніхто більше не думає, поки вони читають, чи ...?
підкреслюю_d

1
@underscore_d Мені подобається, як, на даний момент, ця відповідь говорить: "Можуть виникнути проблеми з цією пропозицією; я не впевнений, що те, що я тільки що сказав, має сенс (!), сміливо коментуйте (!!); і о, до речі, це майже так, як C ++ 17 буде працювати ».
Кайл Странд

1
@KyleStrand Так, це ще одне питання, яке я помітив, але забув згадати серед усіх інших забав. Редагування про C ++ 17 не було зафіксовано ОП ... і ІМО не повинен був бути затверджений, а розміщений як нова відповідь: його можна було б зменшити як "змінити значення повідомлення", навіть якби посада мала почати було безглуздо… Мені не було відомо про редагування - в абсолютно нових розділах була чесна гра, і, звичайно, було відхилено менш різкі зміни, але я думаю, що це удача в розіграші, з точки зору того, які рецензенти ви отримаєте.
підкреслюй_d

27

Ви не можете робити те, що просите, з причин, з яких звернулися інші люди, але ви можете це зробити:

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

що для всіх намірів і цілей - це те саме, що ви просите. Якщо ви любите інкапсуляцію, ви можете зробити make_variable статичною функцією члена. Ось що люди називають іменем конструктора. Отже, він не тільки робить те, що ви хочете, але майже називається тим, що ви хочете: компілятор виводить параметр шаблону з (названого) конструктора.

Примітка: будь-який розумний компілятор оптимізує тимчасовий об'єкт, коли ви пишете щось подібне

auto v = make_variable(instance);

6
Хочете зазначити, що не особливо корисно робити статичний член функцією в такому випадку, оскільки для цього вам доведеться вказувати аргумент шаблону для класу, щоб викликати його так, щоб не було сенсу його виводити.
Предельник

3
А ще краще в C ++ 11, ви можете зробити auto v = make_variable(instance)так, що вам фактично не потрібно вказувати тип
Клавдіу

1
Так, хай ідея оголосити функцію make staticчленом ... подумайте про це на ювенічну секунду. Це в стороні: вільні функції замикаючих були дійсно рішенням, але це багато надлишкових шаблонним, що в той час як ви друкуєте це, ви просто знаєте , ви не повинні , тому що компілятор має доступ до всієї інформації ви повторити. .. і, на щастя, C ++ 17 канонізує це.
підкреслюйте_d

21

У освічений вік 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;

Це відбувається тому, що декларація конструктора копіювання не шаблонована:

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>&);, що, очевидно, було б недоречно.

Відповідь Cătălin Pitiș

Pitiș наводить приклад виведення, Variable<int>а Variable<double>потім констатує:

У мене в коді однакове ім’я типу (Змінна) для двох різних типів (Змінна та Змінна). З моєї суб'єктивної точки зору, це сильно впливає на читабельність коду.

Як зазначалося в попередньому прикладі, Variableсама по собі не є назвою типу, хоча нова функція робить це схожим на одну синтаксичну.

Потім Piti asks запитує, що трапиться, якщо не буде надано жодного конструктора, який би дозволяв відповідне висновок. Відповідь полягає в тому, що не дозволяється робити висновок, оскільки висновок ініціюється викликом конструктора . Без конструктор-виклику немає висновку .

Це схоже на запитання про те, яка версія 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 ++, схоже, вказує на те, що оператор аналізується як оголошення функції.


Яка чудова, детальна відповідь! var4- це лише випадок "найбільш неприємного розбору" (не пов'язаного з відрахуванням аргументів шаблону). Ми використовували для цього просто додаткові паролі, але в цей час я думаю, що використання дужок для однозначного позначення конструкції є звичайною порадою.
Сумуду Фернандо,

@SumuduFernando Дякую! Ви маєте на увазі, що Variable var4(Variable(num));трактується як декларація функції? Якщо так, то чому Variable(num)дійсна специфікація параметра?
Кайл Странд

@SumuduFernando Не маю на увазі, я не мав уявлення, що це справедливо: coliru.stacked-crooked.com/a/98c36b8082660941
Kyle Strand

11

Досі відсутній: Цей код робить досить неоднозначним:

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 <obj> d), то повинен був би бути встановлений певний пріоритет.
GRB

1
Або, як альтернатива, змусити компілятор викинути невизначену помилку параметра шаблону ще раз, як я запропонував щодо відповіді Пітіса. Однак якщо взяти цей маршрут, кількість разів, коли можна дійти висновку без проблем (помилок), стає все менше і менше.
GRB

Це насправді цікавий момент, і (як я зазначив у своїй відповіді) я ще не впевнений, як прийнята пропозиція C ++ 17 вирішує це.
Кайл Странд

9

Припустимо, що компілятор підтримує те, про що ви запитували. Тоді цей код дійсний:

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

Тепер у мене є одне й те саме ім'я (Variable) у коді для двох різних типів (Variable і Variable). З моєї суб'єктивної точки зору, це сильно впливає на читабельність коду. Маючи однакове ім’я типу для двох різних типів в одному просторі імен, мені здається оманливим.

Пізніше оновлення: ще одне, що слід врахувати: часткова (або повна) спеціалізація шаблонів.

Що робити, якщо я спеціалізуюся на змінній та не надаю такого конструктора, як ви очікуєте?

Тому я мав би:

template<>
class Variable<int>
{
// Provide default constructor only.
};

Тоді у мене є код:

Variable v( 10);

Що повинен робити компілятор? Використовуйте загальне визначення класу змінної, щоб визначити, що вона є змінною, а потім виявите, що змінна не забезпечує один конструктор параметрів?


1
Гірше: що робити, якщо у вас є лише Змінна <int> :: Змінна (float)? Тепер у вас є два способи виведення змінної (1f) і немає способу вивести змінну (1).
MSalters

Це хороший момент, але його можна легко перевершити кастингом: Змінна v1 ((подвійний) 10)
jpinto3912

Я погоджуюся, що читабельність коду є суб'єктивною проблемою, проте я згоден на 100% із тим, що ви говорите про спеціалізацію шаблонів. Рішенням, ймовірно, буде дати не визначену помилку параметра шаблону (як тільки компілятор перегляне спеціалізацію <int> і не побачить дійсних конструкторів, скажіть, що вона не має поняття, який шаблон ви хочете використовувати, і що ви повинні вказати явно), але Я згоден, що це не дуже гарне рішення. Я додав би це як ще одну основну синтаксичну дірку, яку потрібно було б вирішити (але її можна було б вирішити, якщо хтось сприйме наслідки).
GRB

4
@ jpinto3912 - ти не вистачаєш точки. Компілятор повинен створити ВСІ можливі змінні <T>, щоб перевірити, чи БУДЬ будь-який ctor Variable <T> :: Variable надає неоднозначний ctor. Позбавлення від неоднозначності не є проблемою - просто інстанціювати змінну <double> самостійно, якщо саме цього ви хочете. Виявлення цієї неоднозначності в першу чергу робить неможливим.
MSalters

6

Стандарт C ++ 03 і C ++ 11 не дозволяє виводити аргументи шаблону з параметрів, переданих конструктору.

Але є пропозиція "Вирахування параметрів шаблону для конструкторів", тож незабаром ви можете отримати те, про що ви просите. Редагувати: справді ця функція підтверджена для C ++ 17.

Дивіться: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html та http://www.open-std.org/jtc1/sc22/wg21/docs/ документи / 2015 / p0091r0.html


Ця функція була додана до C ++ 17, але не, якщо "скоро" застосовується до 6 - 8 років. ;)
ChetS

2

Багато класів не залежать від параметрів конструктора. Є лише кілька класів, у яких є лише один конструктор, і параметризуються на основі типів (ив) цього конструктора.

Якщо ви дійсно потребуєте висновку шаблону, використовуйте функцію помічника:

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
Звичайно, ця функціональність виявиться корисною лише для деяких класів, але те саме можна сказати і для умовиводу функцій. Не всі шаблонні функції також беруть свої параметри зі списку аргументів, але ми дозволяємо робити висновки для тих функцій, які виконують.
GRB

1

Вирахування типів обмежується функціями шаблону в поточному 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

Ви маєте рацію, що компілятор може легко здогадатися, але це не в стандарті або C ++ 0x, наскільки я знаю, тому вам доведеться чекати принаймні ще 10 років (ISO-стандарти з фіксованим оборотом), перш ніж постачальники компіляторів додадуть цю функцію


Це невірно, коли введений стандарт буде введено автоматичне ключове слово. Погляньте на публікацію Джеймса Хопкінса в цій темі. stackoverflow.com/questions/984394/… . Він показує, як це можливо в C ++ 0x.
ovanes

1
Щоб виправити себе, ключове слово auto також присутнє в чинному стандарті, але з різною метою.
ovanes

Схоже, пройде 8 років (з моменту цієї відповіді) ... тож 10 років не було поганою здогадкою, хоча в цей час існували два стандарти!
Кайл Странд

-1

Давайте розглянемо проблему з посиланням на клас, з яким всі повинні бути знайомими - std :: vector.

По-перше, дуже поширеним використанням вектора є використання конструктора, який не приймає жодних параметрів:

vector <int> v;

У цьому випадку, очевидно, ніякий висновок не може бути виконаний.

Друге поширене використання - це створення попереднього розміру вектора:

vector <string> v(100);

Ось, якщо були використані умовиводи:

vector v(100);

ми отримуємо вектор вставки, а не рядки, і, імовірно, він не розмір!

Нарешті, розглянемо конструктори, які приймають кілька параметрів - із "умовиводом":

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

Який параметр слід використовувати для висновку? Нам знадобиться якийсь спосіб сказати компілятору, що він повинен бути другим.

З усіма цими проблемами для класу, простими як векторні, легко зрозуміти, чому умовивод не використовується.


3
Я думаю, що ти нерозумієш цю ідею. Виведення типу для конструкторів відбуватиметься лише в тому випадку, якщо тип шаблону є частиною конструктора. Припустимо, що вектор має шаблон визначення шаблону <typename T>. Ваш приклад не є проблемою, оскільки конструктор вектора визначатиметься як вектор (розмір int), а не вектор (розмір T). Тільки у випадку вектора (розмір T) відбудеться будь-який висновок; у першому прикладі компілятор подасть помилку, сказавши, що T не визначено. По суті ідентичний тому, як працює висновок шаблону функції.
GRB

Отже, це має місце лише для конструкторів, які мають єдиний параметр, і де цей параметр є типом параметра шаблону? Це здається зникаючим невеликим числом екземплярів.

Це не обов'язково повинен бути єдиним параметром. Наприклад, може бути векторний конструктор вектора (int size, T firstElement). Якщо шаблон має кілька параметрів (шаблон <typename T, typename U>), у нього може бути Holder :: Holder (T firstObject, U secondObject). Якщо шаблон має декілька параметрів, але конструктор приймає лише один з них, наприклад, Holder (U secondObject), T завжди повинен бути чітко вказаний. Правила повинні бути максимально схожими на висновок шаблону функції.
GRB

-2

Зробивши ctor шаблоном Змінна може мати лише одну форму, але різні цитри:

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

Побачити? Ми не можемо мати декілька членів змінної :: даних.


Це не було б сенсу за жодного сценарію. obj з точки зору даних obj не визначено, оскільки цей клас більше не є шаблоном. Такий код був би недійсним у будь-якому випадку.
GRB

Я хотів поведінки компілятора, яку ви описуєте, тож я винайшов спосіб обійти це обмеження (в моєму випадку), яке може бути вам цікавим, stackoverflow.com/questions/228620/garbage-collection-in-c-why/…
Нік Дандулакіс,

-2

Додаткову інформацію про це див. У розділі Виведення аргументів шаблону C ++ .


4
Я читав цю статтю раніше, і, здавалося, це мало говорило про те, що я говорю. Єдиний раз, коли письменнику здається, що йдеться про виведення аргументів щодо класів, це той момент, коли він каже, що цього не можна зробити вгорі статті;) - якщо ви можете вказати на розділи, які, на вашу думку, є актуальними, хоча я ' буду дуже вдячний.
GRB
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.