Избегать дублирования требований в предварительном объявлении шаблона?

Играю с Concepts TS в новом проекте. Мой вопрос связан с кажущейся круговой зависимостью, которая у меня есть между шаблоном структуры и связанной концепцией, которую я хочу создать. Конкретная логика концепции состоит в том, чтобы проверить, является ли аргумент типа для концепции специализацией шаблона структуры. Поскольку я хотел бы, чтобы концепция была доступна для использования внутри шаблона структуры, мне, очевидно, нужно определить концепцию перед шаблоном структуры, но тогда логика концепции также должна знать о шаблоне структуры. Я разработал кое-что, что будет компилироваться, путем прямого объявления шаблона структуры Vector, затем определения концепции VectorSpecialization, а затем, наконец, определения шаблона структуры Vector. Мой конкретный вопрос касается того факта, что я использую предложение requires для шаблона структуры; когда я пересылаю объявление об этом, компилятор выдает мне ошибку, если я не дублирую полное предложение requires. (См. Код ниже).

У меня конкретный вопрос: есть ли способ избежать полного дублирования предложения requires между предварительным объявлением и определением шаблона? Одна из возможностей заключалась бы в том, чтобы выделить логику предложения requires в общую сущность, которой можно было бы делегировать как объявление, так и определение, что, как я полагаю, будет относиться к принципу DRY; но мне любопытно, есть ли структурное решение более высокого уровня, которое я мог бы принять здесь, чтобы избежать необходимости даже в предложении requires в обоих местах, или есть более идиоматический способ использования концепций для таких случаев использования, который я мог бы выгода от. Чтобы повторить итерацию, я говорю о следующем варианте использования: написание концепции, которая будет использоваться в шаблоне, но концепция также должна знать о шаблоне.

// Forward declare the struct template so that the concept can refer to it
// Note the need to repeat the 'requires' clause.  Can that repetition be
// be avoided?
template< typename T, size_t N > requires N > 1 struct Vector;

// compile-time overload set using template arg deduction to detect
// when the argument is a specialization of 'Vector'
template< typename NonVector >
    constexpr bool IsVectorSpecialization( NonVector && ) {
        return false;
}
template< typename T, size_t N >
constexpr bool IsVectorSpecialization( Vector<T, N> && ) {
    return true;
}

// The concept, which uses the above overloaded constexpr function
template< typename VectorCandidate >
concept bool VectorSpecialization_CV
        = IsVectorSpecialization( std::declval<VectorCandidate>() );

template< typename T, size_t N >
requires N > 1
struct Vector : std::array<T, N> {
    // Some function templates with VectorSpecialization parameters, e.g.
    //     T dot( VectorSpecialization const &other ) const;
    // ...
};

(Примечание: помимо конкретного вопроса, я также приветствовал бы обсуждение (в комментариях, конечно) аспектов дизайна Concepts TS, которые имеют отношение к этому вопросу, и / или решений, которые предлагают люди, поскольку часть Причина, по которой я играю с Concepts TS, состоит в том, чтобы увидеть, насколько хорошо он работает на практике, чтобы увидеть, есть ли какие-либо полезные отзывы для комитета до полной стандартизации. Например, есть ли настройка в дизайне "Concepts Lite", которая могла бы убрать необходимость дублировать такие requires предложения?)


person Anthony Hall    schedule 02.12.2016    source источник
comment
С образцом кода есть несколько проблем: (1) концепция называется VectorSpecialization_CV, но в другом месте упоминается как VectorSpecialization, (2) определение ODR-использует std::declval, что является недопустимым.   -  person Casey    schedule 02.12.2016
comment
@ Кейси, не могли бы вы подробнее рассказать о том, что std::declval - нет-нет? Он компилировался для меня, но это потому, что я еще не создавал экземпляр шаблона функции dot; как только я добавил код для этого, компилятор (GCC) сообщил мне error: call to non-constexpr function, имея в виду std::declval<VectorCandidate>(), на который вы указали. Я достаточно хорошо понимаю эту ошибку. Но есть ли что-то большее, чем то, о чем вы говорили?   -  person Anthony Hall    schedule 03.12.2016
comment
Это достаточно близко;) Стандарт явно запрещает ODR-использование declval ([declval] / 2), поскольку реализации только декларируют это. Использование ODR в constexpr контексте неправильно по двум причинам.   -  person Casey    schedule 03.12.2016


Ответы (1)


Одна из гарантий, предоставляемых ограниченными шаблонами, заключается в том, что при каждом названии специализации шаблона параметры должны соответствовать ограничениям. См. P121R0 §14.3 [temp.names] / 8:

Когда имя-шаблона идентификатора-простого-шаблона называет ограниченный нефункциональный шаблон или ограниченный шаблон параметр-шаблона, но не шаблон элемента, который является членом неизвестной специализации (14.7), и все аргументы-шаблона в simple-template-id не зависят от 14.6.2.4, связанные ограничения ограниченного шаблона должны быть удовлетворены. (14.10.2).

В контексте вашего примера это означает, например, что неправильно называть Vector<int, 1> даже без его создания:

template< typename T, size_t N > requires N > 1 struct Vector;
using foo = Vector<int, 1>*;
// ill-formed: constraints not satisfied: '(N > 1)' evaluated to false

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

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

template<typename T>
requires true
bool foo(T) { return true; }

template<typename T>
requires false
bool foo(T) { return false; }

Это совершенно допустимая программа, которая объявляет два перегруженных шаблона функций с именем foo, второй из которых никогда не будет выбран при разрешении перегрузки. Опять же, связанные ограничения являются важной особенностью объявлений. При каждом объявлении необходимо повторять связанные ограничения, как и имя сущности шаблона или количество и виды параметров.

Концепции - это механизм, который язык предоставляет для управления этим повторением: вместо того, чтобы повторять огромные выражения ограничений снова и снова, мы даем им удобные имена. N > 1 вряд ли является достаточно обременительным, чтобы заслужить именованную концепцию для экономии нажатий клавиш, но обеспечивает единую точку определения для идея явно полезна:

template< size_t N > concept bool VectorLength = N > 1;
template< typename T, VectorLength N > struct Vector;

template< typename >
constexpr bool IsVectorSpecialization = false;
template< typename T, size_t >
constexpr bool IsVectorSpecialization<Vector<T, N>> = true;

template< typename VC >
concept bool VectorSpecialization = IsVectorSpecialization<VC>;

template< typename T, VectorLength N >
struct Vector : std::array<T, N> {
    T dot( VectorSpecialization const& );
};

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

person Casey    schedule 02.12.2016
comment
requires false Это совершенно правильная программа. Не совсем, поскольку нет действительных специализаций, см. temp # inst-17 - person Jarod42; 28.07.2021