Advertisement

Problem with variable arguments

Started by November 08, 2015 02:46 AM
29 comments, last by Aardvajk 9 years, 1 month ago

Read this, and then read this, it should fix your problems.

Hint: Your code says:


new C(args...))

Where does the variable 'args' come from? Your code doesn't declare any variable by that name.

Bonus hint:

Your template function is still in your .cpp file, despite me saying that was a bad idea. smile.png

Non-template functions go in .cpp files, but template functions go in .h files. There are exceptions to that guideline, but learn the guideline itself before worrying about the exceptions.

Unrelated:

After you get your code working, you'll want to look into std::make_unique().

You shouldn't do std::unique_ptr<C>(new C());

It works, but it's less safe and less efficient than std::make_unique().

It works, but it's less safe and less efficient than std::make_unique().


That is not completely true. Using std::make_unique<> is indeed slightly safer (but only in extremely limited circumstances which you don't meet on a regular basis). std::make_unique<> is, however, not any more efficient. That's different from std::make_shared which is more efficient (by doing the whole allocation in a single call instead of allocating the object and its bookkeeping structure in two calls).

One problem with std::make_unique<> is that you cannot specify the deleter. Granted, a stateful deleter in an std::unique_ptr is uncommon but can be required.

On the balance of things I would still prefer to use std::make_unique<> where possible.
Advertisement
You appear to be using the "code randomly until it compiles" approach that we see now and again. Its not a good approach with even simple C++, but certainly not feasible with varadic templates.

You need to start far simpler and build up to what you want, understanding each stage. Here's a varadic template function that forwards its arguments:

void f(int i, float f){ }
void f(bool b){ }

template<class... Args> void t(const Args&... args)
{
    f(args...);
}
Note specifically how we define the parameters to the function, then reference these again by name in the call. The Args and args can be any name, doesn't matter, for example:

template<class... Bongo> void t(const Bongo&... bingo)
{
    f(bingo...);
}
The same syntax applies with member functions. You can consider args... inside the function to be an "unpacking" of the template parameters, with commas between them, so calling the function with an int and a float is equivalent to:

template<class... Args> void t(const Args&... args)
{
    f(args...);
}

t(10, 23.0f);

// essentially the compiler "generates" (although probably doesn't really)
void t(int i, float f)
{
    f(i, f);
}
Play around with that. Make sure you get what is happening, then try to apply what you have learned to your specific use case.

I maybe should have used std::foward by the way. I've not personally quite figured out when this is and isn't required yet. But I have lots of working code based on the above.

Ok, I've changed it to:


// In the header
template <class C, typename... Args>
void addObj(const Args&... args);
// In the .cpp file
template <class C, typename... Args>
void Gui::GuiHandler::addObj(const Args&... args)
{
    container.push_back(std::unique_ptr<C>(new C(args...)));
}

But it still results in the same error :( :


error: statement cannot resolve address of overloaded function

@Servant, I completely forgot to declare args... D: and isn't std::make_unique only for C++14? I'm using C++11.

Bonus hint:
Your template function is still in your .cpp file, despite me saying that was a bad idea. smile.png
Non-template functions go in .cpp files, but template functions go in .h files. There are exceptions to that guideline, but learn the guideline itself before worrying about the exceptions.

Bonus hint:
Your template function is still in your .cpp file, despite me saying that was a bad idea. smile.png
Non-template functions go in .cpp files, but template functions go in .h files. There are exceptions to that guideline, but learn the guideline itself before worrying about the exceptions.

So, should I make the declaration of the template function in the header file itself? like:


// menuhandler.hpp
class GuiHandler
{
    public:
        std::vector<std::unique_ptr<GuiBase>> container;
        GuiHandler();
        template <class C, typename... Args>
        void Gui::GuiHandler::addObj(const Args&... args)
        {
            container.push_back(std::unique_ptr<C>(new C(args...)));
        }
        void drawElements(sf::RenderWindow& window); 
};

?

Advertisement
Combining declaration and definition in one is certainly one way to deal with it. It's also possible to write the definition later in the header or move it into a separate include file (often called .inl for inline). Unless the definition is extremely long and makes the declarations difficult to read I would favor declaration and definition in one.

Nevermind, it now compiles/links without any errors biggrin.png

All I had to do to remove that error by changing:


guiHandler.addObj<Gui::Button, 10.f, 20.f, 25.f, 25.f, "Button_1", MainFont, 30u>;

to:


guiHandler.addObj<Gui::Button>(10.f, 20.f, 25.f, 25.f, "Button_1", MainFont, 30u);

Tho, a new error appeared. But I managed to fix it by changing some things in the constructor of Gui::Button biggrin.png

std::make_unique<> is, however, not any more efficient. That's different from std::make_shared which is more efficient (by doing the whole allocation in a single call instead of allocating the object and its bookkeeping structure in two calls).

Oops, you're right. A unique pointer doesn't need the shared data struct. Thanks for the correction!

I've used custom deleters for std::unique_ptr before, but never realized std::make_unique didn't provide an interface for it. mellow.png

@louie999: Congrats on getting it working!

The things in the <> brackets are what get used to generate a real function from the template function.

The things in the () brackets are what get passed to the function after its been generated.


#include <iostream>
using namespace std;

template<typename Type, int FixedTemplatedValue>
void TemplatedFunc(Type value)
{
	std::cout << "Fixed templated value: " << FixedTemplatedValue
	          << "\tPassed in changable value: " << value << std::endl;
}

int main()
{
	auto FuncSeventeen = &TemplatedFunc<std::string, 17>;
	auto FuncTwoHundred = &TemplatedFunc<float, 200>;
	
	//NOTE: These functions always print "17" as their fixed value, 
	//because it's now BUILT INTO the function.
	//They also always take a std::string as their first parameter, because it's BUILT IN at compile time,
	//but they let you decide at run time what the value of that string is.
	FuncSeventeen("Meow");
	FuncSeventeen("Purr"); 
	
	//NOTE: These functions always print "200" as their fixed value, 
	//because it's now BUILT INTO the function.
	//They also always take a float as their first parameter, because it's BUILT IN at compile time,
	//but they let you decide at run time what the value of that float is.
	FuncTwoHundred(0.257f);
	FuncTwoHundred(1.5f);   
	
	return 0;
}

This prints:


Fixed templated value: 17	Passed in changable value: Meow
Fixed templated value: 17	Passed in changable value: Purr
Fixed templated value: 200	Passed in changable value: 0.257
Fixed templated value: 200	Passed in changable value: 1.5

Test it here

When you do:


TemplatedFunc<std::string, 17>("Meow");

It's the same as:


auto FuncSeventeen = &TemplatedFunc<std::string, 17>;
FuncSeventeen("Meow");

The compiler at compile-time creates the function "TemplatedFunc<std::string, 17>" from the template, by passing in the template arguments <std::string> and <17>.

Then, at run-time, the program calls the function, with the function arguments ("Meow").


@Servant, I completely forgot to declare args... D: and isn't std::make_unique only for C++14? I'm using C++11.
Yes, std::make_unique() was forgotten in C++11, so they added it in C++14.
However, you could just add it to your code yourself, and then remove it when you get access to the real thing.

//std::make_unique implementation (it was forgotten in the C++11 standard, and will be added later).
//Once it's added, I can just remove this function from here.
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args )
{
	return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

Or better yet, you could just enable C++14 in your compiler, and start using those features. It doesn't break any of your existing code, and adds some cool features.

But if your current compiler doesn't support it, then I understand.

This topic is closed to new replies.

Advertisement