在模板的前向声明中避免重复的 requires 子句?
Avoid duplicate requires clause in forward declaration of template?
我正在新项目中使用 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;
// ...
};
(注意:除了具体问题,我也欢迎讨论(当然是在评论中)关于与这个问题相关的概念 TS 设计的各个方面 and/or人们提供的解决方案,因为我使用 Concepts TS 的部分原因是看它在实践中的效果如何,看看在完全标准化之前是否有任何有用的反馈给委员会。例如,是否有调整"Concepts Lite" 的设计可以消除像这样重复 requires
子句的需要?)
约束模板提供的保证之一是,无论何时命名模板特化,参数都必须满足约束。参见 P121R0 §14.3 [temp.names]/8:
When the template-name of a simple-template-id names a constrained non-function template or a constrained template template-parameter, but not a member template that is a member of an unknown specialization (14.7), and all template-arguments in the simple-template-id are non-dependent 14.6.2.4, the associated constraints of the constrained template shall be satisfied. (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>
constexpr bool always_false = false;
template<typename T>
requires always_false<T>
bool foo(T) { return false; }
这是一个完全有效的程序,它声明了两个名为 foo
的重载函数模板,其中第二个模板永远不会被重载决议选中。同样,关联的约束是声明的一个显着特征。在每个声明中重复关联的约束就像重复模板实体的名称或参数的数量和种类一样有必要。
概念是语言提供的管理这种重复的机制:我们不是一遍又一遍地重复大量的约束表达式,而是为它们提供方便的名称。 N > 1
很难繁琐到需要一个命名概念来节省击键,但是 providing a single point of definition for the notion is clearly worthwhile:
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& );
};
定义这种结构真的别无选择。概念不能前向声明的事实有时很烦人,但我发现将设计分解为线性结构以避免无法表达的循环依赖是很容易理解的。
我正在新项目中使用 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;
// ...
};
(注意:除了具体问题,我也欢迎讨论(当然是在评论中)关于与这个问题相关的概念 TS 设计的各个方面 and/or人们提供的解决方案,因为我使用 Concepts TS 的部分原因是看它在实践中的效果如何,看看在完全标准化之前是否有任何有用的反馈给委员会。例如,是否有调整"Concepts Lite" 的设计可以消除像这样重复 requires
子句的需要?)
约束模板提供的保证之一是,无论何时命名模板特化,参数都必须满足约束。参见 P121R0 §14.3 [temp.names]/8:
When the template-name of a simple-template-id names a constrained non-function template or a constrained template template-parameter, but not a member template that is a member of an unknown specialization (14.7), and all template-arguments in the simple-template-id are non-dependent 14.6.2.4, the associated constraints of the constrained template shall be satisfied. (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>
constexpr bool always_false = false;
template<typename T>
requires always_false<T>
bool foo(T) { return false; }
这是一个完全有效的程序,它声明了两个名为 foo
的重载函数模板,其中第二个模板永远不会被重载决议选中。同样,关联的约束是声明的一个显着特征。在每个声明中重复关联的约束就像重复模板实体的名称或参数的数量和种类一样有必要。
概念是语言提供的管理这种重复的机制:我们不是一遍又一遍地重复大量的约束表达式,而是为它们提供方便的名称。 N > 1
很难繁琐到需要一个命名概念来节省击键,但是 providing a single point of definition for the notion is clearly worthwhile:
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& );
};
定义这种结构真的别无选择。概念不能前向声明的事实有时很烦人,但我发现将设计分解为线性结构以避免无法表达的循环依赖是很容易理解的。