Шаблон псевдонима C ++ 17 с аргументами класса шаблона по умолчанию - PullRequest
0 голосов
/ 02 мая 2018

Кажется, что в C ++ 17 добавлена ​​возможность отбрасывать «<>» в классах шаблонов, когда все аргументы имеют значения по умолчанию (точно так же, как мы могли делать с функциями в течение длительного времени), например:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };

int main()
{
    MyStruct<2> a;
    MyStruct<> b; // old way to use defaults
    MyStruct c; // new way to use defaults
    return 0;
}

Однако кажется, что эта функция больше не работает при использовании шаблона псевдонима, например ::100100

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };

template<int LENGTH = 1>
using MyAlias = MyStruct<LENGTH>;

int main()
{
    MyAlias<2> a;
    MyAlias<> b; // old way still works
    MyAlias c; // new way doesn't compile:
    // gcc 7.3: missing template arguments before 'c'
    // clang 6.0.0: declaration of variable 'c' with deduced type 'MyAlias' requires an initializer
    return 0;
}

Это кажется мне неожиданным поведением. Есть ли какие-либо обходные пути, позволяющие отбрасывать "<>"? (Я знаю, что отдельная typedef может быть создана с другим именем, например: using MyAlias2 = MyStruct <>, но я хочу такое же точное имя. Я также знаю, что определение может обмануть его, например, #define MyAlias ​​MyStruct, но предположим, что это только в крайнем случае.)

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Есть ли какие-нибудь обходные пути, позволяющие отбрасывать "<>"?

Возможный обходной путь может быть прозрачным наследованием:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };

template<int LENGTH = 1>
struct MyAlias : MyStruct<LENGTH> { };

int main()
{
    MyAlias<2> a;
    MyAlias<> b;
    MyAlias c;
    return 0;
}

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

Это кажется мне неожиданным поведением.

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

MyStruct obj;

до

template <int LENGTH=1>
MyStruct<LENGTH> f() { return MyStruct<Length>{ }; }

auto obj = f();

Однако для псевдонимов вы можете сделать что-то вроде этого:

template <int LENGTH = 1>
using MyAlias = MyStruct<LENGTH + 1>;

Приведенное выше преобразование полностью пропустило бы это "+ 1", если бы оно просто заменило имя MyAlias на MyStruct, подразумевая, что нет тривиального решения этой проблемы - но к этому времени нигде в стандарте этот случай не обрабатывается поэтому понятно, что он не скомпилируется.

0 голосов
/ 03 мая 2018

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

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };

#define MyAlias(...) MyStruct<__VA_ARGS__>
using MyAlias = MyStruct<>;

int main()
{
    MyAlias(2) a; // MyAlias<2>
    MyAlias() b; // MyAlias<>
    MyAlias c;
}

Есть несколько преимуществ по сравнению с другими подходами:

  • Вам не нужен C ++ 17
  • Вам не нужно заново указывать значения по умолчанию (DRY принцип)
  • Вам не нужны дополнительные классы, функции и т. Д.
  • Может сочетаться с обходным путем для специализации псевдонимов шаблонов

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

В идеале я бы хотел иметь возможность использовать шаблоны псевдонимов:

template<int BITS>
struct BasicReal final
{
    constexpr BasicReal(const double){};
};

template<int BITS = 64>
using Real = BasicReal<BITS>;

template<> // Alias template specialization if it worked
using Real<64> = double;

template<> // Alias template specialization if it worked
using Real<32> = float;

int main()
{
    Real r = 1.2; // Alias template argument deduction if it worked
    Real<16> r16 = 1.2;
    Real<32> r32 = 1.2;
    Real<64> r64 = 1.2;
    return r;
}

Однако сейчас я могу использовать следующий обходной путь (даже в C ++ 11):

template<int BITS>
struct BasicReal final
{
    constexpr BasicReal(const double){};
};

template<int BITS = 64>
struct RealAlias
{
    using Type = BasicReal<BITS>;
};

template<>
struct RealAlias<64>
{
    using Type = double;
};

template<>
struct RealAlias<32>
{
    using Type = float;
};

#define Real(...) RealAlias<__VA_ARGS__>::Type
using Real = RealAlias<>::Type;

int main()
{
    Real r = 1.2;
    Real(16) r16 = 1.2;
    Real(32) r32 = 1.2;
    Real(64) r64 = 1.2;
    return r;
}
0 голосов
/ 02 мая 2018

Это кажется мне неожиданным поведением. Существуют ли какие-либо обходные пути, позволяющие удалить «<>»?

Это ожидаемое поведение. Ну, зависит от того, что вы ожидаете, я думаю. Вычет аргумента шаблона класса только применяется в контексте использования имени шаблона основного класса без указания каких-либо параметров шаблона и только в контексте создания объектов.

Он не применяется в контексте шаблонов псевдонимов (как показано в OP). И это не применяется в контексте вывода шаблона функции.

Если кто-то не предложит изменить это, обходной путь должен просто написать MyAlias<>.

<ч />

Существует предложение обобщить объявление using, чтобы гипотетически вы могли написать using MyAlias = MyStruct; и иметь этот шаблон псевдонима. В этом контексте было бы разумно разрешить MyAlias c;, поскольку MyAlias непосредственно называет шаблон класса.

Но общая проблема более сложна, поскольку шаблоны псевдонимов могут выполнять такие действия, как изменение порядка типов или добавление новых. И вам придется отвечать на такие вопросы, как:

template <typename T> using tuple_int = std::tuple<T, int>;
tuple_int t(4, 2);   // would this work? how?
tuple_int u(4, '2'); // what about this?

Я не говорю, что нет ответа. Я просто говорю, что это не тривиальная вещь.

...