启用优化时 C++ 模板的行为不同(发布)

C++ template behaves different when optimization is enabled (release)

上下文:

目标是能够写出如下内容:

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。我最大的问题!

优化器是否过于激进并删除了 X::X() ctor???但是在 gcc 和 VisualStudio 中都有相同的编译器错误实在是太巧合了。 我错过了什么或做错了什么?

Q1。 static_assert

这是 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 关于完美转发的评论也帮助我找到了最终解决方案:

工作解决方案: 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;
}

感谢大家的帮助!