Avoid duplicate requires clause in forward declaration of template?

时间:2016-12-02 04:55:01

标签: c++ c++-concepts

I'm playing with the Concepts TS in a new project. My question has to do with a seeming circular dependency I have between a struct template and an associated concept I want to create. The specific logic of the concept is to check that a type argument to the concept is a specialization of the struct template. Since I'd like the concept to be available for use inside the struct template, I apparently need to define the concept before the struct template, but then the logic of the concept needs to know about the struct template as well. I have worked out something that will compile, by forward declaring the struct template Vector, then defining the concept VectorSpecialization, then finally defining the struct template Vector. My specific question relates to the fact that I use a requires clause for the struct template; when I forward declare it, the compiler gives me an error unless I duplicate the full requires clause. (See code below).

My specific question is: is there way to avoid the full duplication of the requires clause between the template's forward declaration and definition? One possibility would be to factor out the logic of the requires clause into a common entity that both the declaration and definition could delegate to, which I suppose would address the DRY principle; but I'm curious if there's a higher-level structural decision I could have made here to avoid the need of even having a requires clause in both places, or if there's a more idiomatic way of using concepts for use cases like this that I could benefit from. To re-iterate, the use case I'm speaking of is: writing a concept that will be used in a template, but the concept also needs to know about the template.

// 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;
    // ...
};

(Note: Aside from the specific question, I'd also welcome discussion (in the comments, of course) about aspects of the Concepts TS design that bear on this question and/or solutions that people offer, since part of the reason I'm playing with the Concepts TS is to see how well it works in practice, to see if there's any useful feedback for the committee before full standardization. For example, is there a tweak to the design of "Concepts Lite" that could remove the need to duplicate requires clauses like this?)

1 个答案:

答案 0 :(得分:3)

受约束模板提供的保证之一是,无论何时命名模板特化,参数都必须满足约束。见P121R0§14.3[temp.names] / 8:

  

simple-template-id template-name 命名受约束的非功能模板或受约束的模板 template-parameter 时,但不是作为未知专业化成员的成员模板(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非常繁琐,不值得使用命名概念来保存击键,但是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& );
};

定义这种结构真的别无选择。概念不能被前向声明这一事实有时很烦人,但我发现已经分解成线性结构的设计很难理解,这些设计是避免不可能表达的循环依赖所必需的。