Я хотел бы сделать тип, который оборачивает числовой тип (и обеспечивает дополнительную функциональность).
Кроме того, мне нужен номер и обертка, чтобы быть оба неявно конвертируемых друг другу.
Пока что у меня есть:
template<class T>
struct Wrapper
{
T value;
Wrapper() { }
Wrapper(T const &value) : value(value) { }
// ... operators defined here ...
};
Это почти хорошо, но это не так довольно ведут себя так же, как встроенный тип:
#include <iostream>
int main()
{
unsigned int x1, x2 = unsigned int();
Wrapper<unsigned int> y1, y2 = Wrapper<unsigned int>();
std::cerr << x1 << std::endl; // uninitialized, as expected
std::cerr << y1.value << std::endl; // uninitialized, as expected
std::cerr << x2 << std::endl; // zero-initialized, as expected
std::cerr << y2.value << std::endl; // uninitialized!?!
}
Есть ли способ для меня, чтобы спроектировать Wrapper
такие, что заявления типа
Wrapper<unsigned int> y2 = Wrapper<unsigned int>();
инициализировать value
внутри, но без принуждение заявления как
Wrapper<unsigned int> y1;
также сделать то же самое?
Другими словами, возможно ли сделать тип, который ведет себя именно так такой же как встроенный тип с точки зрения инициализации?
Обновленный ответ
Хорошо, как указывает Дип, я и все остальные были неправы. Вы можете достичь того, что вы хотите сделать, = default
с конструктором по умолчанию:
Wrapper() = default ;
^^^^^^^^^
Это работает, потому что без инициализатора вы получаете то же поведение, которое я обрисовал ранее, но когда вы используете инициализацию значения, поведение меняется, как описано в параграфе 8:
— если T является (возможно, cv-квалифицированным) типом класса, не являющимся объединением без предоставленного пользователем или удаленного конструктора по умолчанию, объект инициализируется нулями и, если T имеет нетривиальный конструктор по умолчанию, default-initialized;
Оригинальный ответ
Я не думаю, что есть способ заставить эту работу работать так, как вам хотелось бы. Типы классов действуют иначе, чем встроенные типы, которые мы можем видеть из раздела проекта стандарта 8.5
Инициализаторы параграф 12 который говорит (Акцент шахты идет вперед):
Если для объекта не указан инициализатор, объект инициализируется по умолчанию; если инициализация не выполняется, объект с автоматическим или динамическим сроком хранения имеет неопределенное значение. [Примечание: объекты со статическим или потоковым хранением инициализируются нулями, см. 3.6.2. —Конечная записка]
и мы можем видеть, что это имеет разные результаты для классов, чем встроенные типы из абзаца 7 который говорит:
По умолчанию инициализировать объект типа T означает:
и включает в себя следующие пули:
— если T является (возможно, cv-квалифицированным) Тип класса (раздел 9), конструктор по умолчанию для T называется (и инициализация некорректна, если у T нет доступного конструктора по умолчанию);
— если T является типом массива, каждый элемент инициализируется по умолчанию;
— в противном случае инициализация не выполняется.
и если мы посмотрим на абзац 11 для второго случая Wrapper<unsigned int>()
это говорит:
Объект, инициализатором которого является пустой набор скобок, т.е. (), должен быть инициализирован значением.
а затем вернуться к абзацу 8:
Инициализировать значение объекта типа T означает:
— если T является (возможно, cv-квалифицированным) тип класса (Раздел 9) без конструктора по умолчанию (12.1) или конструктора по умолчанию, предоставленного или удаленного пользователем, затем объект инициализируется по умолчанию; […]
Таким образом, мы в конечном итоге с таким же поведением.
И то и другое преторианец а также aschepler дал вам варианты, которые работают немного по-другому, но, кажется, для достижения желаемого поведения, но не с тем же синтаксисом.
Я не думаю, что есть какой-то способ достичь того, что вы ищете. Как только вы определите конструктор по умолчанию для класса, который будет вызываться, будете ли вы указывать или опускать скобки при определении экземпляра класса.
Вы можете немного приблизиться, объявив следующий конструктор; определение переменной потребует пустой пары фигурных скобок для достижения инициализации значения.
Wrapper(std::initializer_list<std::initializer_list<T>> /*unused*/) : value() {}
auto y3 = Wrapper<unsigned int>({}); // y3.value will be value initialized
Но я бы скорее отменил требование неявного преобразования в Wrapper
и сохранить класс агрегатом, чем реализовать решение выше.
К сожалению, не то, чтобы я мог думать. C ++ неявно преобразует class_type name
вызвать конструктор по умолчанию. Вам придется заставить конструктор по умолчанию делать то, что вы ожидаете от неинициализированного примитивного типа.
Если вы удалите предоставленный пользователем конструктор, вы можете оставить элемент неинициализированным при конструировании по умолчанию или инициализировать значение оберткой и при этом инициализировать нулем его хранилище (и, следовательно, его член):
unsigned int x1, x2 {}; // One uninitialized, one value-initialized
Wrapper<unsigned int> y1, y2 {}; // Ditto
Вы все еще можете установить значение во время построения с помощью агрегатной инициализации:
Wrapper<int> z {42};
Во всяком случае, это в значительной степени не нужно; неинициализированные значения редко бывают полезны, кроме как для выявления тонких, трудно воспроизводимых ошибок. Я бы порекомендовал инициализировать значение члена либо в конструкторе по умолчанию, либо в объявлении члена.