You might also overload your function several times to match all possible patterns, but it is a messy solution. For this, first of all, you might need to change your char parameters to either char* (C-style string) or std::string, because otherwise (if it even compiles) calling an overloaded function with different sized integer types (int and char) is ambiguous and may result in an undefined behavior.
void triangle(int n, int start=1, int spcs=0, std::string dec_y="n", std::string lf="#", std::string decf="o") {
//some code
}
void triangle(int n, std::string dec_y="n", std::string lf="#", int start=1, int spcs=0, std::string decf="o") {
//some code
}
Then you can write
triangle(9, "y", "&"); // using 2-nd overload (int, std:;string, std::string) where n=9, dec_y="y", lf="&"
but again, this is not really recommended, especially for functions with this much arguments, because it might (and will) mess the code up if there will be a need for more such overloads. Also, the problem with this solution is that if you won't pass any argument to the default parameter the call will also be ambiguous.
In my recent experience I've done a similar thing in my project when I decided to do this:
Node(std::string name, std::string path = "", Node* parent = NULL, int pos = -1);
Node(std::string name, Node* parent = NULL, int pos = -1, std::string path = "");
and that did work as a solution for me to make things easier when I wanted only to specify "path" leaving "parent" and "pos" with their defaults and vice versa.