Capturing lambda and dsp::WaveShaper

the example code for dsp::WaveShaper shows initialization like this:

dsp::WaveShaper<float> ws({ std::tanh });

and the longer form works too:
dsp::WaveShaper<float> ws({[](float sample){ return std::tanh(sample); } })

but if i try to capture anything xcode blows up so this:
dsp::WaveShaper<float> ws({[foo](float sample){ return std::tanh(sample + foo); } })

gives me:
error: no matching constructor for initialization of 'juce::dsp::WaveShaper<float>' ../../JuceLibraryCode/modules/juce_dsp/processors/juce_WaveShaper.h:36:8: note: candidate constructor (the implicit copy constructor) not viable: cannot convert initializer list argument to 'const juce::dsp::WaveShaper<float, float (*)(float)>' struct WaveShaper ^ ../../JuceLibraryCode/modules/juce_dsp/processors/juce_WaveShaper.h:36:8: note: candidate constructor (the implicit move constructor) not viable: cannot convert initializer list argument to 'juce::dsp::WaveShaper<float, float (*)(float)>' ../../JuceLibraryCode/modules/juce_dsp/processors/juce_WaveShaper.h:36:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided 1 error generated.

what am i doing wrong? is there a way to do this?

I’d be curious if you put the function on it’s own line as a std::function<FloatType(FloatType)>, would you get the results you want:

float parm;
std::function<float(float)> shaperFunc = [&](float sample) { return std::tanh(sample + parm); };
dsp::WaveShaper<float> ws(shaperFunc);

thanks, i had tried that too and std::function doesn’t work with or without capturing:
No matching conversion for functional-style cast from 'std::function<float (float)>' to 'juce::dsp::WaveShaper<float>'

Hi,

try dsp::WaveShaper<float, std::function<float (float)>> ws([=](float sample){ return std::tanh(sample + foo); });

explanation: The Waveshaper classes second template argument is the function type. Per default this is a plain c style function pointer - not a std::function (probably for performance reasons). C++11 is clever enough to convert a non-capturing lambda into a c function pointer, but as soon as you’re trying to capture something, you need to use a std::function (to store the captured values). So the solution is to explicitly specify the second template argument of dsp::WaveShaper as std::function<float (float)>

6 Likes

yes! i had to put the lambda in curly braces but this works. thanks!

I think it’s actually because std::function is not allowed as a default parameter type in template declarations.

@reuk got around this with this update to abstractFifo’s update here:

Every usage I’ve seen him share on the discord involves passing a lambda to the call to forEach():

template <typename Arg>
class CommandQueue final
{
public:
    explicit CommandQueue (std::size_t capacity)
        : commands { capacity }, fifo { int (capacity) }
    {
    }

    template <typename Func>
    void push (Func&& func)
    {
        fifo.write (1).forEach ([this, func = std::move (func)] (int index) {
            commands[std::size_t (index)] = std::move (func);
        });
    }

    void execute (Arg arg)
    {
        fifo.read (fifo.getNumReady()).forEach ([this, &arg] (int index) {
            if (const auto& command = commands[std::size_t (index)])
                command (arg);
        });
    }

private:
    std::vector<std::function<void (Arg)>> commands;
    AbstractFifo fifo;
};

Is this a workaround for what you’re saying isn’t possible @jamiebullock?

**UPDATE: my explanation below is not entirely correct, and based on a subtle misunderstanding of the docs. See @pixelpacker’s later post below for details **

@matkatmusic

I’m not sure how what you’re suggesting here relates to capturing lambda in dsp::WaveShaper, but what I’m saying isn’t possible, is to write something like this:

template<typename Function = std::function<float(float)>> class MyClass { };

This, to my knowledge is not allowed by the C++ standard.

This is why I think whoever wrote dsp::WaveShaper used a function pointer for the template parameter’s default type:

template <typename FloatType, typename Function = FloatType (*) (FloatType)>
struct WaveShaper {   };

So, if we declare our WaveShaper instance as follows:

WaveShaper<float> ws;

Then we cannot use a capturing lambda (as stated at the top of the thread), because the type of Function defaults to the function pointer float (*) (float) and a capturing lambda cannot be converted to a C function pointer.

Instead, we must declare our WaveShaper passing a std::function for the type of the second parameter, thus replacing the default i.e.

WaveShaper<float, std::function<float(float)>> ws;

Now a capturing lambda can be assigned to Waveshaper::functionToUse because it has a compatible type, i.e. std::function<float(float)>

Hopefully this makes sense! My point was only that I think a function pointer has been used as the parameter default because a std::function is not allowed and not for efficiency reasons. It was just a minor observation really.

2 Likes

man, thanks for the uber-detailed explanation! I definitely learned something new about templates and one of the juce::DSP classes.

1 Like

I think you may have misunderstood what they wrote. They say, default template arguments are not allowed in function template declarations - which is not the same as “std::function is not allowed as a default template argument”. That is, if you’re not referring to something else.

For what it’s worth, this little test program compiles and runs just fine:

#include <iostream>
#include <functional>

template <typename F=std::function<void ()>> struct S{ F f; };

int main(int argc, const char* const [])
{
	S<> s;
	s.f = [=](){ std::clog << "hello world " << argc << std::endl; };
	s.f();
	return 0;
}

(compiled with clang++ -std=c++11 -Wall -Wextra test.cpp)

1 Like

@pixelpacker Hm… interesting. It seems you are right, thanks for pointing this out.

I was actually referring to this:

type is one of the following types (optionally cv-qualified, the qualifiers are ignored):

But I missed this, which appears below the box on the page:

Array and function types may be written in a template declaration, but they are automatically replaced by pointer to object and pointer to function as appropriate.

So your assumption about using the function pointer default for performance reasons is probably right.

Sorry for the noise.

Well, I see. The section refers to non-type template parameters. i.e. template parameters you’re passing values, not types. for example:

// examples for non-type template parameters

template <int> struct A{}; // you can pass an integer

enum MyEnum
{
	ME_ONE,
	ME_TWO,
};

template <MyEnum> struct B{}; // or an enum value

typedef void (*foo)(int);
template <foo> struct C{}; // or - oddly enough - a function pointer
void bar(int) { }

int main(int, const char* const [])
{
	A<10> a;
	B<ME_ONE> b;
	C<bar> c; // this one puzzled me, now I need to come up with an excuse to use this ;)

	return 0;
}

And for non-type template parameters, one can indeed not use std::function. But oddly enough one can use pointers to objects or functions (as long as the passed value is static). And even pointers to member objects and methods. The more you know …

1 Like

it makes you wonder where something like that would ever be useful… I can’t imagine ever needing to write code that needs templates that accept pointers to member functions as the template argument. I also can’t imagine how that would even look…

it may look like this:

#include <iostream>

struct S
{
	int foo() { return 0; }
	int bar() { return 1; }
};

template <int (S::*memberFunction)()> void magic(S& s)
{
	std::clog << (s.*memberFunction)() << std::endl;
}

int main(int, const char* const [])
{
	S s;
	magic<&S::foo>(s);
	magic<&S::bar>(s);
	return 0;
}

So you can pass a member function like a callback to another function, but because the value of this pointer is static, the compiler may be able to inline the function if it pleases (I think).

1 Like

Interesting! I definitely learned something new in this thread.