This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

PROCESSOR-SDK-AM64X: std::apply - how to use it correctly?

Part Number: PROCESSOR-SDK-AM64X

Hello,

so we are using the MCU+SDK and its Clang Compiler (ti-cgt-armllvm_1.2.1.STS) and we want to use the std::apply function. 

But it does not work since it throws this compiler-error:

In file included from C:\Pr\firmware\modules\balluff-platform\BuildInfrastructure\Sources\balluff-platform\Pattern\..\..\..\..\Sources\balluff-platform\Pattern\Includes\States.hpp:12:
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\tuple:1416:5: error: attempt to use a deleted function
    _VSTD::__invoke_constexpr(
    ^
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\__config:833:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_ABI_NAMESPACE
              ^
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\tuple:1413:26: note: in instantiation of exception specification for '__apply_tuple_impl<void (balluff::platform::bce::InterfaceCalleeFunction<void, balluff::platform::internals::OperationModes>::*)(balluff::platform::internals::OperationModes), std::tuple<balluff::platform::internals::OperationModes> &, 0>' requested here
constexpr decltype(auto) __apply_tuple_impl(_Fn && __f, _Tuple && __t,
                         ^
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\tuple:1425:12: note: in instantiation of function template specialization 'std::__apply_tuple_impl<void (balluff::platform::bce::InterfaceCalleeFunction<void, balluff::platform::internals::OperationModes>::*)(balluff::platform::internals::OperationModes), std::tuple<balluff::platform::internals::OperationModes> &, 0>' requested here
    _VSTD::__apply_tuple_impl(
           ^
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\tuple:1423:26: note: in instantiation of exception specification for 'apply<void (balluff::platform::bce::InterfaceCalleeFunction<void, balluff::platform::internals::OperationModes>::*)(balluff::platform::internals::OperationModes), std::tuple<balluff::platform::internals::OperationModes> &>' requested here
constexpr decltype(auto) apply(_Fn && __f, _Tuple && __t)
                         ^
C:\XX/InterfaceASyncCallee.hpp:84:18: note: in instantiation of function template specialization 'std::apply<void (balluff::platform::bce::InterfaceCalleeFunction<void, balluff::platform::internals::OperationModes>::*)(balluff::platform::internals::OperationModes), std::tuple<balluff::platform::internals::OperationModes> &>' requested here
            std::apply(&InterfaceCalleeFunction<void, TParams...>::call, data_.front());
                 ^
C:\ti\ti-cgt-armllvm_1.2.1.STS\include\c++\v1\type_traits:1916:5: note: '~__nat' has been explicitly marked deleted here
    ~__nat() = delete;
    ^

my call looks like the following:

std::apply(&InterfaceCalleeFunction<void, TParams...>::call, data_.front());


where data_ is just a small queue with the tuple inside:

etl::queue<std::tuple<TParams...>, MAX_QUEUED_ASYNC_CALLS> data_;

my call-function looks like this:

void call(TParams... data);

What I am doing wrong? 

Best regards

Felix

  • Hi Felix,

    will check with the compiler team, thx

  • Because std::apply is a C++ feature introduced in C++17, be sure you build with the option -std=c++17.

    If that does not fix the problem, then please submit a test case.  For the source file that sees these diagnostics ... Build it the same as you do now, but preprocess it.  Change the option -c to -E.  Redirect the build to another file, similar to ...

    % tiarmclang -E [other options] file.cpp > preprocessed_file.txt

    Attach preprocessed_file.txt to your next post.  

    Also show all the build options, exactly as the compiler sees them.  Please copy and paste the text, and do not use a screen shot.

    Thanks and regards,

    -George

  • Hey George, 
    yes we built it with -std=c++17.
    Our compiler-flags are:

    -Wno-gnu-variable-sized-type-not-at-end -mcpu=cortex-r5 -mfloat-abi=hard -mfpu=vfpv3-d16 -Wno-error=ti-macros -Wno-unused-function -fno-rtti -D_DEBUG_=1 -g -std=c++17

    SystemTask.cpp.txt

    the concrete use of std::apply is in a template-hpp-file so I used a cpp-file which includes this header. Hope this works. The header is in the preprocessed file starting at line 26652.

    Thanks and best regards

    Felix

  • Thank you for submitting the test case.  I can reproduce the same behavior.  I am discussing it with others on the team.  I'll get back to you on Monday.

    Thanks and regards,

    -George

  • Hi Felix,

    Unless I'm missing something, I believe you're calling a non-static member function without a 'this' pointer argument. It is a common mistake that is often made while doing indirect calls.

    First, I was able to fully replicate your error message using a stock clang compiler: https://godbolt.org/z/18nx9Y7zG

    #include <tuple>
    
    template<typename Ret, typename... Params>
    class Base {
    public:
      void foo(int x) { }
    
    };
    
    template<typename Ret, typename... Params>
    class Derived : public Base<Ret, Params...> {
    public:
        void doCall() {
            std::apply(&Base<Ret, Params...>::foo,
                       std::tuple(4));
        }
    };
    
    int main() {
        Derived<void, int> MyDerived;
        MyDerived.doCall();
    }

    Then, I noted that 'foo' is a non-static member function. Because of this, there's an implicit 'this' pointer argument. Because of how the library verifies the parameters of the call to be applied, the compiler emits an admittedly cryptic error because the 'this' pointer argument is missing. To resolve this, you can use std::tuple_cat: https://godbolt.org/z/TP1xTMEz3

    #include <stdio.h>
    #include <tuple>
    
    template<typename Ret, typename... Params>
    class Base {
    public:
      virtual void foo(int x) { printf("Hi %d\n", x); }
    
    };
    
    template<typename Ret, typename... Params>
    class Derived : public Base<Ret, Params...> {
    public:
        void doCall() {
            std::apply(&Base<Ret, Params...>::foo,
                       std::tuple_cat(std::tuple(this),
                                      std::tuple(4)));
        }
        void foo(int x) override { printf("Derived %d\n", x); }
    };
    
    int main() {
        Derived<void, int> MyDerived;
        MyDerived.doCall();
    }

    An interesting point to make here is that virtual function overrides are still applicable. Note that in the output, the string printed is 'Derived', indicating that Base<Ret, Params...>::foo resolved to a call to the Derived override.

    Hopefully the addition of a 'this' pointer to the parameter tuple resolves the issue.

    JB

  • So I tried this and changed the previous used call (callWithTuple....) to std:: aplly in the mentioned manner but now it does not work anymore.

    so the previous call was:

    callWithTuple(data_.front(), std::index_sequence_for<TParams...>());
    
    template <std::size_t... Is> void callWithTuple(const std::tuple<TParams...>& tuple, std::index_sequence<Is...>)
    {
        InterfaceCalleeFunction<void, TParams...>::call(std::get<Is>(tuple)...);
    }

    I changed it to

                std::apply(&InterfaceCalleeFunction<void, TParams...>::call,
                           std::tuple_cat(std::tuple(this), data_.front()));


    but somehow it does not work. My derived callee does not get called.

    So I tried to recreate our construct here godbolt.org/.../E7hsaja55

    I implemented our current workaround with the callWithTuple-function. You can just change it to our solution by setting the define CALL_WITH_TUPLE to 1.

    So I see two things: std::apply does not call the base function (but that's what we need). And it produces way more code than our workaround.

    Besides that one would like to mention: What the hell are you doing there? But this is an implementation which guarantees calles to be executed from a synchronous as well from an asynchronous context (so the caller is not the original caller but another task which executes the calls). Synchronous callers directly call and asynchronous ones store them in between, where the call-override happens.

    best regards

    Felix

  • std::apply is defined as using std::invoke to implement its behavior. Thus, I went to std::invoke's definition in the standard. A more or less accurate representation, should you not have access to the standard, can be found here

    Specifically:

    The operation INVOKE(f, t1, t2, ..., tN) is defined as follows:

    • If std::is_base_of<T, std::decay_t<decltype(t1)>>::value is true, then INVOKE(f, t1, t2, ..., tN) is equivalent to (t1.*f)(t2, ..., tN)

    In this case, 'this' is an object of type Derived, and f is a pointer to member function of class Base. Since Base is a base of Derived, the final call we get is:

    std::invoke(&Base<Ret, Params...>::foo, this,
                std::get<0>(data_.front()),
                std::get<1>(data_.front()));
                
    // Equivalent to (With std::is_base_of(Base, decltype(this)) true)
    
    auto fptr = &Base<Ret, Params...>::foo;
    (this->*fptr)(
        std::get<0>(data_.front()),
        std::get<1>(data_.front())
    );

    From the definition of how pointer-to-member calls work, the construct provided in your example and in the Compiler Explorer will always call the derived version of the function. In fact, any attempt using a pointer-to-member to call Base::foo is fruitless because the definition of a call to a pointer-of-member necessitates the behavior of finding the most derived type of the object doing the call and selecting that type's version of the function.

    As far as I know, the only way to call a base class function from a derived class is to do exactly what callWithTuple does, which is to qualify a call to foo with the base type:

    Base<void, Params...>::foo(std::get<Is>(tuple)...);
    
    // Or, more precisely
    this->Base<void, Params...>::foo(std::get<Is>(tuple)...);

    With that knowledge, I think I've found something that may help. Since std::apply can call any callable object, we can give it an object which calls 'this->Base<Ret, Params...>::foo':  a lambda. Doing this, I was able to get 'doCall' to call the Base version of foo.

    https://godbolt.org/z/ThPsj5Mqz

    Note that the output is the same between the call via std::apply and the call via 'callWithTuple', as desired. The only catch with this construct is that the lambda is only valid for as long as 'this' is live. If for some reason you needed to capture this lambda and hold it until some designated time, you can quickly run into use-after-free bugs.

    This has been a great learning experience on my end, as I've not dealt frequently with pointer-to-members. However, I believe this issue has gone beyond being a problem with the TI compiler, and is a C++ language problem. If there aren't any further issues with the TI compiler specifically, I'll close the thread for now.

  • Wow,

    Thank you James. I think you are right. This is not related to the TI-Compiler but more to std-C++ in general. We can close this issue and I learned a lot too here!

    Best regards
    Felix