Mq-b / Modern-Cpp-templates-tutorial

现代C++模板教程
https://mq-b.github.io/Modern-Cpp-templates-tutorial/
Other
662 stars 88 forks source link

concept判定时间 #16

Closed YiRanMushroom closed 9 months ago

YiRanMushroom commented 9 months ago

我自己在写程序的时候发现,concept的判断时间是在定义处,而不是在使用处,换言之,如果先定义了一个concept,之后写了一些代码,影响了concept判定条件,即使在之后使用这个concept,依然会按照修改之前的结果判定,这个是正常的吗。如果是正常的,是否应该强调一下?

Mq-b commented 9 months ago

我自己在写程序的时候发现,concept的判断时间是在定义处,而不是在使用处,换言之,如果先定义了一个concept,之后写了一些代码,影响了concept判定条件,即使在之后使用这个concept,依然会按照修改之前的结果判定,这个是正常的吗。如果是正常的,是否应该强调一下?

举个例子,没看明白要表达什么,请详细描述自己的想法。

YiRanMushroom commented 9 months ago
template<typename T>
    concept is_printable_c = requires(T t){
        std::cout << t;
    };

定义了一个concept。

template<typename T, size_t N>
std::ostream &operator<<(std::ostream &out, const std::array<T, N> &arr)
requires is_printable_c<T> {
    out << "[";
    for (int i = 0; i < N - 1; i++)
        out << arr[i] << ", ";
    out << arr[N - 1] << "]";
    return out;
}

重载了std::ostream&, std::array&运算符。

调用语句

std::array<int, 5> b{1, 2, 3, 4, 5};

std::cout<<is_printable_c<decltype(b)>;

-> 0

新定义一个concept:

template<typename T>
    concept is_printable_after_overloading_c = requires(T t){
        std::cout << t;
    };

调用语句

std::cout<<is_printable_after_overloading_c<decltype(b)>;

-> 1

这两个concept虽然看着内容相同,但是在不同时间定义造成了判段结果不同。

Mq-b commented 9 months ago

我能算出你大概要表达什么,但是你是不是不会 markdown?

16bit-ykiko commented 9 months ago

template concept is_printable_c = requires(T t){ std::cout << t; };

定义了一个concept。

template<typename T, size_t N> std::ostream &operator<<(std::ostream &out, const std::array<T, N> &arr) requires is_printable_c { out << "["; for (int i = 0; i < N - 1; i++) out << arr[i] << ", "; out << arr[N - 1] << "]"; return out; }

重载了std::ostream&, std::array&运算符。

调用语句 std::array<int, 5> b{1, 2, 3, 4, 5};

std::cout<<is_printable_c<decltype(b)>; -> 0

新定义一个concept: template concept is_printable_after_overloading_c = requires(T t){ std::cout << t; };

调用语句 std::cout<<is_printable_after_overloading_c<decltype(b)>; -> 1

这两个concept虽然看着内容相同,但是在不同时间定义造成了判段结果不同。

这本质上是因为编译器会缓存常量求值的结果,编译器认为任意两个相同的模板实例化总是应该具有相同的结果。所以在第一次求值之后,会缓存结果。之后的求值都会使用第一次求值的结果替代。所以和第一次模板实例化的位置有关。

详细的讨论参考:

https://zhuanlan.zhihu.com/p/646752343

Mq-b commented 9 months ago

这个问题在上古年代就说过,只是我觉得偏,懒得做视频,我在外面过年,手机扣字没办法为你展示更多东西。

你能意识到这个问题很好,并且提出来了,你有自己的思考,再说。

但是你的语法格式让我绷不住,尤其我手机看,即使是修改后,而且你应该附带网页运行的链接,而不是打算让我们测试自己看,不方便我们,以及其他人观看和了解你的问题。

frederick-vs-ja commented 9 months ago

如果在不同位置求值约束满足条件结果不同,则程序非良构不要求诊断。这个设计来自 P2104R0

更严谨的编译器可能应该报错。虽然不报错也没问题。

YiRanMushroom commented 9 months ago

https://godbolt.org/z/MW7crn7xY 这个是可供测试的代码

Mq-b commented 9 months ago

如果在不同位置求值约束满足条件结果不同,则程序非良构不要求诊断。这个设计来自 P2104R0

更严谨的编译器可能应该报错。虽然不报错也没问题。

简单明了。

YiRanMushroom commented 9 months ago

就是说这种写法本身是有问题的?不应该在concept定义后再更改一些值使concept判定会出现不同结果。

16bit-ykiko commented 9 months ago

就是说这种写法本身是有问题的?不应该在concept定义后再更改一些值使concept判定会出现不同结果。

不应该依赖于全局状态的改变,这种写法确实不应该提倡。

16bit-ykiko commented 9 months ago

https://godbolt.org/z/MW7crn7xY 这个是可供测试的代码

这里有一个更简单的复现代码,用于测试类型完整性的,事实上和该类型第一次实例化的位置有关,和定义的位置关系不大。

https://godbolt.org/z/dGqsaxG8z

代码不应该依赖这些可以观测到的全局状态改变,否则容易造成 ODR 违背

YiRanMushroom commented 9 months ago

明白了,就是说在concept实例化后就不应该修改,会造成concept判断不一致的状态,是吗?