How to use CTAD for a lambda?

376 views Asked by At

Good afternoon, everyone. I implemented a couple of classes:

// CallingDelegate
template <typename Result, typename ... Args>
class CallingDelegate
{
    using TypeDelegate = std::function<Result(Args...)>;
public:
    CallingDelegate() = delete;
    CallingDelegate(const std::shared_ptr<TypeDelegate>& boxDelegate) : m_boxDelegate(boxDelegate) {}
public:
    Result operator()(Args... args) const
    {
        if (m_boxDelegate)
        {
            const auto& delegate = (*m_boxDelegate.get());
            if (delegate)
            {
                return delegate(std::forward<Args>(args)...);
            }
        }
        return {};
    }
private:
    const std::shared_ptr<TypeDelegate> m_boxDelegate;
};
// HolderDelegate
template <typename Result, typename ... Args>
class HolderDelegate
{
    using TypeDelegate = std::function<Result(Args...)>;
public:
    using TypeCalling  = CallingDelegate<Result, Args...>;
public:
    ~HolderDelegate() { (*m_boxDelegate.get()) = nullptr; }
    HolderDelegate() = delete;
    HolderDelegate(const TypeDelegate& delegate) : m_boxDelegate(std::make_shared<TypeDelegate>    (delegate)) {}
    //
    template<typename TypeCallback>
    HolderDelegate(const TypeCallback& callback) : m_boxDelegate(std::make_shared<TypeDelegate>    (TypeDelegate(callback))) {}
public: /// NON COPY
    HolderDelegate(const HolderDelegate&) = delete;
    HolderDelegate& operator=(const HolderDelegate& other) = delete;
public:
    inline TypeCalling getCalling() const { return TypeCalling(m_boxDelegate); }
private:
    const std::shared_ptr<TypeDelegate> m_boxDelegate;
};

you can use it like this:

// Func
void runDelegate(const std::function<std::string(size_t)> delegate)
{
    for (size_t i = 0; i < 3; ++i)
    {
        printf("index: %zu => text: %s\n", i, delegate(i).c_str());
    }
}
// Main
int main()
{
    std::function<std::string(size_t)> delegate;
    {
        const HolderDelegate<std::string, size_t> holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
        delegate = holder.getCalling();
        runDelegate(delegate);
    }
    runDelegate(delegate);
    return 0;
}

In the 17 standard, you can use the following:

// Main
int main()
{
    std::function<std::string(size_t)> delegate;
    {
        const std::function<std::string(size_t)> delegateRaw = [] (const size_t index) -> std::string {     return std::to_string(index); };
        const HolderDelegate holder (delegateRaw);
        delegate = holder.getCalling();
        runDelegate(delegate);
    }
    runDelegate(delegate);
    return 0;
}

But you can't use it like this (using lambda):

// Main
int main()
{
    std::function<std::string(size_t)> delegate;
    {
        const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
        delegate = holder.getCalling();
        runDelegate(delegate);
    }
    runDelegate(delegate);
    return 0;
}

Maybe I missed the standard information somewhere, or maybe it can't be implemented. Tell me please. I will be glad of any information.

Error:

main.cpp: In function 'int main()':
main.cpp:65:104: error: class template argument deduction     failed:
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
main.cpp:65:104: error: no matching function for call to     'HolderDelegate(main()::<lambda(size_t)>)'
main.cpp:44:2: note: candidate: 'template<class Result, class     ... Args> HolderDelegate(const HolderDelegate<Result, Args>&)->     HolderDelegate<Result, Args>'
  HolderDelegate(const HolderDelegate&) = delete;
  ^~~~~~~~~~~~~~
main.cpp:44:2: note:   template argument deduction/substitution     failed:
main.cpp:65:104: note:   'main()::<lambda(size_t)>' is not     derived from 'const HolderDelegate<Result, Args>'
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
main.cpp:42:2: note: candidate: 'template<class Result, class     ... Args, class TypeCallback> HolderDelegate(const TypeCallback&)->     HolderDelegate<Result, Args>'
  HolderDelegate(const TypeCallback& callback) :     m_boxDelegate(std::make_shared<TypeDelegate>(TypeDelegate(callback))) {}
  ^~~~~~~~~~~~~~
main.cpp:42:2: note:   template argument deduction/substitution     failed:
main.cpp:65:104: note:   couldn't deduce template parameter     'Result'
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
main.cpp:39:2: note: candidate: 'template<class Result, class     ... Args> HolderDelegate(const TypeDelegate&)-> HolderDelegate<Result, Args>'
  HolderDelegate(const TypeDelegate& delegate) :     m_boxDelegate(std::make_shared<TypeDelegate>(delegate)) {}
  ^~~~~~~~~~~~~~
main.cpp:39:2: note:   template argument deduction/substitution     failed:
main.cpp:65:104: note:   'main()::<lambda(size_t)>' is not     derived from 'const TypeDelegate'
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
main.cpp:38:2: note: candidate: 'template<class Result, class     ... Args> HolderDelegate()-> HolderDelegate<Result, Args>'
  HolderDelegate() = delete;
  ^~~~~~~~~~~~~~
main.cpp:38:2: note:   template argument deduction/substitution     failed:
main.cpp:65:104: note:   candidate expects 0 arguments, 1     provided
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
main.cpp:31:7: note: candidate: 'template<class Result, class     ... Args> HolderDelegate(HolderDelegate<Result, Args>)-> HolderDelegate<Result,     Args>'
 class HolderDelegate
       ^~~~~~~~~~~~~~
main.cpp:31:7: note:   template argument deduction/substitution     failed:
main.cpp:65:104: note:   'main()::<lambda(size_t)>' is not     derived from 'HolderDelegate<Result, Args>'
   const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });
                                                                                                            ^
mingw32-make[1]: *** [Makefile.Debug:105: debug/main.o] Error 1
2

There are 2 answers

3
cigien On

If your lambda doesn't capture anything, then you can decay it to a function pointer at the call site:

const HolderDelegate holder ( + [] (const size_t index) -> std::string { return std::to_string(index); });
                           // ^ decay to FP

and then you can add a deduction guide for HolderDelegate:

template <typename Result, typename ... Args>
HolderDelegate(Result (*)(Args...)) -> HolderDelegate<Result, Args...>;

Here's a demo.

2
max66 On

It's complicated...

With

    const std::function<std::string(size_t)> delegateRaw = [] (const size_t index) -> std::string {     return std::to_string(index); };
    const HolderDelegate holder (delegateRaw);

you pass a std::function<std::string(std::size_t)> to the HolederDelegate constructor that has a constructor waiting for a std::function.

So the new (C++17) automatic deduction guides detect HolderDelegates as HolderDelegates<std::string, std::size_t>.

But if you pass a lambda function to the HolderDelegates constructor

    const HolderDelegate holder ([] (const size_t index) -> std::string {     return std::to_string(index); });

you have a sort of chicken-and-egg problem because the lambda can be converted to a std::function but isn't a std::function.

So the types of the std::function can't be deduced and the automatic deduction guides doesn't works.

To go around this problem I see tree possible solutions.

The first one is your working way

    const std::function<std::string(size_t)> delegateRaw = [] (const size_t index) -> std::string {     return std::to_string(index); };
    const HolderDelegate holder (delegateRaw);

The second one is explicit the types of HolderDelegates so no deduction is needed at all

    // .................VVVVVVVVVVVVVVVVVVVVVVVvvv
    const HolderDelegate<std::string, std::size_t> holder ([] (const size_t index) -> std::string {     return std::to_string(index); });

The third one is involve the deduction guides for std::function,

    // ..........................VVVVVVVVVVVVVV...........................................................................-V
    const HolderDelegate holder (std::function([] (const size_t index) -> std::string {     return std::to_string(index); }));