Почему бы не вывести параметр шаблона из конструктора?

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

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
}

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

Ответ 1

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

MyClass m(string s);
MyClass *pm;
*pm = m;

Я не уверен, было ли так очевидно, чтобы анализатор знал, какой тип шаблона является MyClass pm;

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

С++ 17

Принято, что С++ 17 будет иметь вывод типа из аргументов конструктора.

Примеры:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

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

Ответ 2

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

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

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

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

Variable<T> v = make_variable(instance);

Ответ 3

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

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

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

Устранение проблем из других ответов

Текущий текущий рейтинг

В этом ответе указывается, что "конструктор копирования и 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 не является типом (it шаблон), поэтому не имеет смысла объявлять указатель типа MyClass. Вот один из возможных способов исправить пример:

MyClassstring m("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

Здесь pm уже имеет правильный тип, и поэтому вывод является тривиальным. Кроме того, невозможно случайно смешать типы при вызове copy-constructor:

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>&); создается. Если он явно не указан или не объявлен конструктор templatized, нет никакой причины для компилятора создавать экземпляр MyClass<int>::MyClass(const MyClass<string>&);, что, очевидно, было бы неуместным.

Ответ Cătălin Pitiş

Pitiş дает пример, выводящий Variable<int> и Variable<double>, затем указывает:

У меня одноименное имя (переменная) в коде для двух разных типов (переменная и переменная). С моей субъективной точки зрения это сильно влияет на читаемость кода.

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

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

Это похоже на вопрос о том, какая версия 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++, по-видимому, указывает на то, что оператор анализируется как объявление функции.

Ответ 4

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

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

Ответ 5

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

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

// Some code here

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

Теперь у меня одно и то же имя типа (Variable) в коде для двух разных типов (Variable and Variable). С моей субъективной точки зрения это сильно влияет на читаемость кода. Имея одно и то же имя типа для двух разных типов в одном и том же пространстве имен, мне кажется, что вводят в заблуждение.

Позднее обновление: Другое дело: частичная (или полная) специализация шаблонов.

Что делать, если я специализирую Variable и не создаю конструктор, как вы ожидаете?

Итак, я бы:

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

Тогда у меня есть код:

Variable v( 10);

Что должен делать компилятор? Используйте общее определение класса Variable, чтобы вывести, что оно является переменным, а затем обнаружить, что Variable не предоставляет один конструктор параметров?

Ответ 6

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

Но есть предложение "Вычисление параметров шаблона для конструкторов", поэтому вы можете получить то, о чем вы просите в ближайшее время. Изменить: действительно, эта функция была подтверждена для С++ 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

Ответ 7

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

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

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

Ответ 8

То, что вы пытаетесь достичь, называется стиранием типа. Взгляните на boost:: any и его реализацию, как это работает.

Boost Any

Теперь на вопрос, почему это не работает. Поскольку boost:: any демонстрирует, это можно реализовать. И это работает, но вашей проблемой будет отправка, какой тип действительно внутри. В отличие от шаблонного подхода вам потребуется выполнять диспетчеризацию во время выполнения приложения, поскольку содержащийся тип будет удален. Есть две возможности справиться с этим: шаблон посетителя и пользовательская реализация заливки (которая генерирует исключение, если вы пытаетесь использовать неверный тип). В обоих случаях вы переносите безопасность типа времени компиляции во время выполнения и обходите проверки типа компилятора.

Другой подход - ввести тип Variant: Boost Variant

Вариант работает по-разному как boost:: any и позволяет хранить только ограниченное количество типов. Это обеспечивает более высокий уровень безопасности, поскольку вы действительно ограничиваете набор ожидаемых типов. Есть хорошая статья, написанная Андреем Александреску в ddj, о том, как реализовать такой вариант: Часть 1, Часть 2, Часть 3

Реализация немного сложнее, чем функция boost:: any, но обеспечивает более высокий уровень безопасности и запрещает пользователям добавлять любые возможные типы в вариант, за исключением тех, которые явно объявлены.

Как я уже сказал, он может быть реализован на С++, но требует глубокого знания языка и хорошего дизайна интерфейса, так что пользователи этого класса не обрабатывают яблоки в качестве персиков.

Пожалуйста, помните слова Генри Спенсера: Если вы лжете компилятору, он отомстит.

Привет,

Ованес


Идея реализации (! untested!)

class any_type
{
    class any_container
    {
    public:
        virtual ~any_container(){}
        virtual void* pointer()=0;
        virtual type_info const& get_type()const=0;
    };

    template<class T>
    class any_container_impl
    {
        T t_;

    public:
        any_container_impl(T const& t)
            : t_(t)
        {}

        virtual ~any_container_impl(){}

        virtual void* pointer()
        {
            return &t_;
        }

        virtual type_info const& get_type()const
        {
            return typeid(T);
        }

    };

    std::auto_ptr<any_container> content_;

public:
    template<class T>
    any_type(T const& t)
        : content_(new any_container_impl<T>(t))
    {}

    template<class T>
    T* cast_to_ptr()
    {
        if(typeid(T)!=content_->get_type())
            return NULL;
        return reinterpret_cast<T*>(content_->pointer());
    }

    template<class T>
    T& cast_to_ref()
    {
        T* ptr = cast_to_ptr<T>();
        if(!ptr)
            throw std::logic_error("wrong type");
        return *ptr;
    }
};

Ответ 9

Выделение типов ограничено функциями шаблонов в текущем С++, но давно поняли, что вывод типа в других контекстах будет очень полезен. Следовательно, С++ 0x auto.

В то время как то, что вы предлагаете, невозможно в С++ 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>
}

Ответ 10

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

Ответ 11

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

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

vector <int> v;

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

Второе общее использование заключается в создании вектора предварительно заданного размера:

vector <string> v(100);

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

vector v(100);

мы получаем вектор ints, а не строки, и, предположительно, он не имеет размера!

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

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

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

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

Ответ 12

Создавая шаблон 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;         
}

См? Мы не можем иметь несколько переменных: data members.