启用优化时 C++ 模板的行为不同(发布)
C++ template behaves different when optimization is enabled (release)
上下文:
- Invocable:class operator() 为一些不同的参数集重载
- Delegater:与 Invocable 相同,但使用委托(“invocable”)作为第一个参数;可以在每个
Delegater::operator(Delegate&& delegate, ArgsB...)
中调用不同的 delegate.operator(ArgsA...)
重载(注意 ArgsA!=ArgsB)
- 具有可调用项(产生另一个“可调用项”)的委托人的组合 是通过右关联
operator>>=()
完成的
目标是能够写出如下内容:
Delegater d0{0};//the ctor's arg is just an ID
Delegater d1{1};
Delegater d2{2};
Invocable i{9};
auto pipe = d0 >>= d1 >>= d2 >>= i;
pipe(1234);//=> d0(int=1234) calling d1(T1_int...) calling d2(T2...) calling i(...)
pipe(78.9);//=> d0(double=78.9) calling d1(T1_double...) calling d2(T2...) calling i(...)
Q0。我最大的问题!
- 启用优化(VisualStudio 中的 gcc
-O3
或 Release
)并且 X::X ctor 中没有 printf
(第 25 行)=> 运行时崩溃!
- 没有优化它在有或没有
printf
. 的情况下都可以正常工作
优化器是否过于激进并删除了 X::X() ctor???但是在 gcc 和 VisualStudio 中都有相同的编译器错误实在是太巧合了。
我错过了什么或做错了什么?
Q1。 static_assert
- 我在泛型重载
Invocable::operator(Args&&...args)
中有一个 static_assert 尤其是为了捕获未处理的情况,例如使用没有显式重载的参数类型调用 Invocable::operator() (例如“ char*" 在我的示例中,第 ~120 行)。
- 它在 VisualStudio 中按预期工作,当我尝试调用时生成编译时断言
invocable("some text")
...但是 gcc 始终生成编译时断言,即使该行被注释掉。
这是 gcc 错误还是我做错了什么?
一个工作示例:https://godbolt.org/z/MshrcvYKr
完成最小示例:
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{};//only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct X0{};//only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe>)
struct X//eXecutor: d>>=i, d>>=(d>>=i)
: public X0
{
L& l;
R& r;
X(L& l, R& r)
: l{l}
, r{r}
{
printf("X{this=%p} ::X(l=%p, r=%p)\n", this, &l, &r);//commenting this line leads to crash with optimizations enabled! is ctor discarded???
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,X0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return X<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && std::derived_from<R,X0>)
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= rvalueX
return X<L,R>{l, r};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept {
printf("ERR unhandled case! %s\n", std::source_location::current().function_name());
//static_assert(false, "unhandled case");//works on VisualStudio but not on gcc
assert(("unhandled case", false));//helps catching not handled cases
}
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
public:
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
delegate((double)arg);//invoke delegate with some args (not necessary the same as Args...)
}
template<typename Delegate>
//requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
delegate((int)arg);//invoke delegate with some args (not necessary the same as Args...)
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Invocable i{9};
//i("123");//why I cannot catch this at compile time with gcc???
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
return 0;
}
答案:
A0. 感谢@Igor Tandetnik 注意到“我最大的问题”实际上是我最大的基本错误:忘记了临时实例,一旦它们不存在就会被销毁在实际使用我的“管道”之前表达需要!它与上述 printf
一起工作纯属巧合
A1. 感谢@Jarod42 的解释和更好的解决方案:使用=delete;
在编译时检测未处理的情况
template<typename...Args> void operator()(Args&&...args) noexcept = delete;
@François Andrieux 关于完美转发的评论也帮助我找到了最终解决方案:
- 在“组合”中捕获 Delegater 和 Invocable 的实例通过引用(在 struct Di{...} 中)
- 使用完美转发捕获临时对象(in struct Ddi{...})
工作解决方案: https://godbolt.org/z/PdzTT1YGs
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{}; //only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct Di0{}; //only for class tagging & overloaded operators constraining
struct Ddi0{}; //only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
struct Di //d >>= i
: public Di0
{
L& l;
R& r;
Di(L& l, R& r)
: l(l)
, r(r)
{
//printf("%s{this=%p} ::%s(&l=%p, &r=%p)\n", __func__, this, __func__, &l, &r);
}
Di(L& l, R&& r) = delete;
~Di(){
//printf("%s{this=%p &l=%p, &r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Di{this=%p &l=%p, &r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
struct Ddi //d >>= d >>= i
: public Ddi0
{
L& l;
R r;
Ddi(L& l, R& r) = delete;
Ddi(L& l, R&& r)
: l{l}
, r{std::forward<R>(r)}
{
//printf("%s{this=%p &l=%p r=%p} ::%s(&l=%p, &&r=%p) this->r=std::move(r)\n", __func__, this, &this->l, &this->r, __func__, &l, &r);
}
~Ddi(){
//printf("%s{this=%p &l=%p r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Ddi{this=%p &l=%p r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return Di<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= lvaluePipe >>= ... >>= lvalueInvocable
return Ddi<L,R>{l, std::move(r)};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept = delete;//helps catching not handled cases at compile time
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
//delegate((int)arg);
delegate((double)arg);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
delegate((int)arg);
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Delegater d3{3};
Invocable i{9};
static_assert(!std::is_invocable_v<Invocable,char*>);
//i("123");//catched @compile time
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= i\n");
auto dddi = d0 >>= d1 >>= d2 >>= i;
dddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= d3 >>= i\n");
auto ddddi = d0 >>= d1 >>= d2 >>= d3 >>= i;
ddddi(123);
printf("END\n");
return 0;
}
这是我在 OP 末尾添加的“工作解决方案”部分。
工作解决方案: https://godbolt.org/z/PdzTT1YGs
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{}; //only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct Di0{}; //only for class tagging & overloaded operators constraining
struct Ddi0{}; //only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
struct Di //d >>= i
: public Di0
{
L& l;
R& r;
Di(L& l, R& r)
: l(l)
, r(r)
{
//printf("%s{this=%p} ::%s(&l=%p, &r=%p)\n", __func__, this, __func__, &l, &r);
}
Di(L& l, R&& r) = delete;
~Di(){
//printf("%s{this=%p &l=%p, &r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Di{this=%p &l=%p, &r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
struct Ddi //d >>= d >>= i
: public Ddi0
{
L& l;
R r;
Ddi(L& l, R& r) = delete;
Ddi(L& l, R&& r)
: l{l}
, r{std::move(r)}
{
//printf("%s{this=%p &l=%p r=%p} ::%s(&l=%p, &&r=%p) this->r=std::move(r)\n", __func__, this, &this->l, &this->r, __func__, &l, &r);
}
~Ddi(){
//printf("%s{this=%p &l=%p r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Ddi{this=%p &l=%p r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return Di<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= lvaluePipe >>= ... >>= lvalueInvocable
return Ddi<L,R>{l, std::move(r)};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept = delete;//helps catching not handled cases at compile time
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
public:
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
//delegate((int)arg);
delegate((double)arg);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
delegate((int)arg);
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Delegater d3{3};
Invocable i{9};
static_assert(!std::is_invocable_v<Invocable,char*>);
//i("123");//catched @compile time
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= i\n");
auto dddi = d0 >>= d1 >>= d2 >>= i;
dddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= d3 >>= i\n");
auto ddddi = d0 >>= d1 >>= d2 >>= d3 >>= i;
ddddi(123);
printf("END\n");
return 0;
}
感谢大家的帮助!
上下文:
- Invocable:class operator() 为一些不同的参数集重载
- Delegater:与 Invocable 相同,但使用委托(“invocable”)作为第一个参数;可以在每个
Delegater::operator(Delegate&& delegate, ArgsB...)
中调用不同的delegate.operator(ArgsA...)
重载(注意 ArgsA!=ArgsB) - 具有可调用项(产生另一个“可调用项”)的委托人的组合 是通过右关联
operator>>=()
完成的
目标是能够写出如下内容:
Delegater d0{0};//the ctor's arg is just an ID
Delegater d1{1};
Delegater d2{2};
Invocable i{9};
auto pipe = d0 >>= d1 >>= d2 >>= i;
pipe(1234);//=> d0(int=1234) calling d1(T1_int...) calling d2(T2...) calling i(...)
pipe(78.9);//=> d0(double=78.9) calling d1(T1_double...) calling d2(T2...) calling i(...)
Q0。我最大的问题!
- 启用优化(VisualStudio 中的 gcc
-O3
或Release
)并且 X::X ctor 中没有printf
(第 25 行)=> 运行时崩溃! - 没有优化它在有或没有
printf
. 的情况下都可以正常工作
优化器是否过于激进并删除了 X::X() ctor???但是在 gcc 和 VisualStudio 中都有相同的编译器错误实在是太巧合了。 我错过了什么或做错了什么?
Q1。 static_assert
- 我在泛型重载
Invocable::operator(Args&&...args)
中有一个 static_assert 尤其是为了捕获未处理的情况,例如使用没有显式重载的参数类型调用 Invocable::operator() (例如“ char*" 在我的示例中,第 ~120 行)。 - 它在 VisualStudio 中按预期工作,当我尝试调用时生成编译时断言
invocable("some text")
...但是 gcc 始终生成编译时断言,即使该行被注释掉。
这是 gcc 错误还是我做错了什么?
一个工作示例:https://godbolt.org/z/MshrcvYKr
完成最小示例:
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{};//only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct X0{};//only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe>)
struct X//eXecutor: d>>=i, d>>=(d>>=i)
: public X0
{
L& l;
R& r;
X(L& l, R& r)
: l{l}
, r{r}
{
printf("X{this=%p} ::X(l=%p, r=%p)\n", this, &l, &r);//commenting this line leads to crash with optimizations enabled! is ctor discarded???
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,X0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return X<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && std::derived_from<R,X0>)
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= rvalueX
return X<L,R>{l, r};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept {
printf("ERR unhandled case! %s\n", std::source_location::current().function_name());
//static_assert(false, "unhandled case");//works on VisualStudio but not on gcc
assert(("unhandled case", false));//helps catching not handled cases
}
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
public:
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
delegate((double)arg);//invoke delegate with some args (not necessary the same as Args...)
}
template<typename Delegate>
//requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
delegate((int)arg);//invoke delegate with some args (not necessary the same as Args...)
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Invocable i{9};
//i("123");//why I cannot catch this at compile time with gcc???
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
return 0;
}
答案:
A0. 感谢@Igor Tandetnik 注意到“我最大的问题”实际上是我最大的基本错误:忘记了临时实例,一旦它们不存在就会被销毁在实际使用我的“管道”之前表达需要!它与上述 printf
A1. 感谢@Jarod42 的解释和更好的解决方案:使用=delete;
在编译时检测未处理的情况
template<typename...Args> void operator()(Args&&...args) noexcept = delete;
@François Andrieux 关于完美转发的评论也帮助我找到了最终解决方案:
- 在“组合”中捕获 Delegater 和 Invocable 的实例通过引用(在 struct Di{...} 中)
- 使用完美转发捕获临时对象(in struct Ddi{...})
工作解决方案: https://godbolt.org/z/PdzTT1YGs
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{}; //only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct Di0{}; //only for class tagging & overloaded operators constraining
struct Ddi0{}; //only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
struct Di //d >>= i
: public Di0
{
L& l;
R& r;
Di(L& l, R& r)
: l(l)
, r(r)
{
//printf("%s{this=%p} ::%s(&l=%p, &r=%p)\n", __func__, this, __func__, &l, &r);
}
Di(L& l, R&& r) = delete;
~Di(){
//printf("%s{this=%p &l=%p, &r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Di{this=%p &l=%p, &r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
struct Ddi //d >>= d >>= i
: public Ddi0
{
L& l;
R r;
Ddi(L& l, R& r) = delete;
Ddi(L& l, R&& r)
: l{l}
, r{std::forward<R>(r)}
{
//printf("%s{this=%p &l=%p r=%p} ::%s(&l=%p, &&r=%p) this->r=std::move(r)\n", __func__, this, &this->l, &this->r, __func__, &l, &r);
}
~Ddi(){
//printf("%s{this=%p &l=%p r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Ddi{this=%p &l=%p r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return Di<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= lvaluePipe >>= ... >>= lvalueInvocable
return Ddi<L,R>{l, std::move(r)};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept = delete;//helps catching not handled cases at compile time
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
//delegate((int)arg);
delegate((double)arg);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
delegate((int)arg);
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Delegater d3{3};
Invocable i{9};
static_assert(!std::is_invocable_v<Invocable,char*>);
//i("123");//catched @compile time
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= i\n");
auto dddi = d0 >>= d1 >>= d2 >>= i;
dddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= d3 >>= i\n");
auto ddddi = d0 >>= d1 >>= d2 >>= d3 >>= i;
ddddi(123);
printf("END\n");
return 0;
}
这是我在 OP 末尾添加的“工作解决方案”部分。
工作解决方案: https://godbolt.org/z/PdzTT1YGs
#include <cassert>
#include <cstdio>
#include <source_location>
#include <type_traits>
#include <utility>
//----------------------------------------------------------------
struct Pipe{}; //only for filter class tagging & overloaded operators constraining
namespace Pipeline{//right-associative pipe fitting
struct Di0{}; //only for class tagging & overloaded operators constraining
struct Ddi0{}; //only for class tagging & overloaded operators constraining
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
struct Di //d >>= i
: public Di0
{
L& l;
R& r;
Di(L& l, R& r)
: l(l)
, r(r)
{
//printf("%s{this=%p} ::%s(&l=%p, &r=%p)\n", __func__, this, __func__, &l, &r);
}
Di(L& l, R&& r) = delete;
~Di(){
//printf("%s{this=%p &l=%p, &r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Di{this=%p &l=%p, &r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L,typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
struct Ddi //d >>= d >>= i
: public Ddi0
{
L& l;
R r;
Ddi(L& l, R& r) = delete;
Ddi(L& l, R&& r)
: l{l}
, r{std::move(r)}
{
//printf("%s{this=%p &l=%p r=%p} ::%s(&l=%p, &&r=%p) this->r=std::move(r)\n", __func__, this, &this->l, &this->r, __func__, &l, &r);
}
~Ddi(){
//printf("%s{this=%p &l=%p r=%p} ::%s()\n", __func__+1, this, &l, &r, __func__);
}
template<typename...Args>
requires (std::is_invocable_v<L,R,Args...>)
auto operator()(Args&&...args) noexcept {
//printf("Ddi{this=%p &l=%p r=%p} ::%s()\n", this, &l, &r, __func__);
return l(r, std::forward<Args>(args)...);
}
};
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && !std::derived_from<R,Pipe> && !std::derived_from<R,Di0> && !std::derived_from<R,Ddi0>)
auto operator>>=(L& l, R& r) noexcept {//for: lvalueDelegater0 >>= lvalueInvocable
return Di<L,R>{l, r};
}
template<typename L, typename R>
requires (std::derived_from<L,Pipe> && (std::derived_from<R,Di0> || std::derived_from<R,Ddi0>))
auto operator>>=(L& l, R&& r) noexcept {//for: lvaluePipe >>= lvaluePipe >>= ... >>= lvalueInvocable
return Ddi<L,R>{l, std::move(r)};
}
}
using Pipeline::operator>>=;
//----------------------------------------------------------------
struct Invocable{
int id = 0;
Invocable(int id=0) noexcept
: id(id)
{
printf("Invocable{this=%p id=%d} ::Invocable(id=%d)\n", this, id, id);
}
template<typename...Args>
void operator()(Args&&...args) noexcept = delete;//helps catching not handled cases at compile time
void operator()(int arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(int=%d)\n", this, id, __func__, arg);
}
void operator()(double arg) noexcept {
printf("Invocable{this=%p id=%d} ::%s(double=%lf)\n", this, id, __func__, arg);
}
};
//----------------------------------------------------------------
struct Delegater
: public Pipe
{
int id = 0;
Delegater(int id=0) noexcept
: id(id)
{
printf("Delegater{this=%p id=%d} ::Delegater(id=%d)\n", this, id, id);
}
public:
template<typename Delegate, typename...Args>
requires (std::is_invocable_v<Delegate,Args...>)
void operator()(Delegate&& delegate, Args&&...args){//forwards to delegate
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, args...) %s\n", this, id, __func__, &delegate, std::source_location::current().function_name());
delegate(std::forward<decltype(args)>(args)...);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, double>)
void operator()(Delegate&& delegate, int arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, int=%d)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
//delegate((int)arg);
delegate((double)arg);
}
template<typename Delegate>
requires (std::is_invocable_v<Delegate, int>)
void operator()(Delegate&& delegate, double arg){
printf("Delegater{this=%p id=%d} ::%s(delegate=%p, double=%lf)\n", this, id, __func__, &delegate, arg);
//invoke delegate with some args (not necessary the same as Args...)
delegate((int)arg);
}
};
//----------------------------------------------------------------
int main(){
printf("-------- creation\n");
Delegater d0{0};
Delegater d1{1};
Delegater d2{2};
Delegater d3{3};
Invocable i{9};
static_assert(!std::is_invocable_v<Invocable,char*>);
//i("123");//catched @compile time
printf("-------- d0 >>= i\n");
auto di = d0 >>= i;
di(123);
printf("-------- d0 >>= d1 >>= i\n");
auto ddi = d0 >>= d1 >>= i;
ddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= i\n");
auto dddi = d0 >>= d1 >>= d2 >>= i;
dddi(123);
printf("-------- d0 >>= d1 >>= d2 >>= d3 >>= i\n");
auto ddddi = d0 >>= d1 >>= d2 >>= d3 >>= i;
ddddi(123);
printf("END\n");
return 0;
}
感谢大家的帮助!