9

I have designed a parameter class which allows me to write code like this:

//define parameter
typedef basic_config_param<std::string> name;

void test(config_param param) {

  if(param.has<name>()) { //by name
    cout << "Your name is: " << param.get<name>() << endl;
  }

  unsigned long & n = param<ref<unsigned long> >(); //by type
  if(param.get<value<bool> >(true)) { //return true if not found
    ++n;
  }
}


unsigned long num = 0;
test(( name("Special :-)"), ref<unsigned long>(num) )); //easy to add a number parameter
cout << "Number is: " << num; //prints 1

The performance of the class is pretty fast: everything is just a reference on the stack. And to save all the information I use an internal buffer of up to 5 arguments before it goes to heap allocation to decrease the size of every single object, but this can be easily changed.

Why isn't this syntax used more often, overloading operator,() to implement named parameters? Is it because of the potential performance penalty?

One other way is to use the named idiom:

object.name("my name").ref(num); //every object method returns a reference to itself, allow object chaining.

But, for me, overloading operator,() looks much more "modern" C++, as long you don't forget to uses double parentheses. The performance does not suffer much either, even if it is slower than a normal function, so is it negligible in most cases.

I am probably not the first one to come up with a solution like this, but why isn't it more common? I have never seen anything like the syntax above (my example) before I wrote a class which accepts it, but for me looks it perfect.

Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
Fredrik
  • 893
  • 2
  • 10
  • 20
  • 31
    ...because it adds complexity and just isn't that useful? – Ed S. Apr 05 '12 at 23:58
  • 7
    What does this achieve? – Oliver Charlesworth Apr 05 '12 at 23:59
  • 5
    Also, overloading `operator,` is usually a bad idea, because it won't behave like the normal `,` operator (similar logic explains why one shouldn't overload `operator&&` or `operator||`). – Oliver Charlesworth Apr 06 '12 at 00:01
  • 4
    Yeah, I mean, I see a lot of complexity here and little or nothing gained. Remember; complexity costs money. It takes time for people to understand what is going on ($$$) and it takes time to maintain/fix bugs associated with it (again, $$$). Honestly, if I saw something like that in the code I work on I would have to have a chat with whomever implemented it. This to me smacks of something that an inexperienced dev would come up with. – Ed S. Apr 06 '12 at 00:05
  • 4
    This functionality has been available in boost for years: http://www.boost.org/doc/libs/1_49_0/libs/parameter/doc/html/index.html don't know how much it is used in practice, though – Pablo Apr 06 '12 at 00:08
  • Nothing gained? I can add any number of parameters (in any order) with minimal code change, I achieve great flexibility. There are of course some rules, but the implementation forces them on the user so much it can. Boost.parameter increases the compile time greatly, it should not my solution do. It also uses multiple macros which in my opinion is not so much "modern C++" – Fredrik Apr 06 '12 at 00:17
  • 3
    @Fredrik: and again, how does that help you in an appreciable way? Sure, it sounds cool, but are people *really* having difficulty delivering robust code on time because they have to pass arguments to a function in a certain order? I don't think so. – Ed S. Apr 06 '12 at 00:23
  • @Fredrik : "*Boost.parameter increases the compile time greatly*" Use precompiled headers and you won't see an issue. Why anyone _doesn't_ use them at this point is a complete mystery to me. Blaming a library because you're using your toolset ineffectively is silly. – ildjarn Apr 06 '12 at 00:29
  • Well, it don´t fits every solution/function, the class design should only help you if you have a large number of parameters. Everyone can easily just pass some types in wrong order. But I understand your points, thank you! – Fredrik Apr 06 '12 at 00:35
  • This example is non-human-readable enough to put average code maintainer into infinite loop... The point of using templates is to make compiler guess template arguments automatically when it is possible. Ideally, code that calls template functions/methods should not contain any `<` or `>` brackets at all (classes are another story, but they should be typedeffed). Something like `unsigned long & n = param >()` is incomprehensible. However, something like `unsigned long n; getParam(n);` where `getParam` is templated ` void getParam(T&)`, is much easier to read. – SigTerm Apr 06 '12 at 00:40
  • @Fredrik: "Boost.parameter increases the compile time greatly" compile time costs less than development time, IMO. How long does it take to compile? Hour? And how long does it take to implement your solution or to explain it to somebody? – SigTerm Apr 06 '12 at 00:43
  • @ildjarn : I know it, excuse me, but what I really mean, was that it is too huge, in my opinion. I want an single file, simple as that. My class is 200-250 lines of code, how large is boost.parameter if you include every dependency (with an exception of STL). I believe it is pretty much, but I am not a boost freak. – Fredrik Apr 06 '12 at 00:44
  • @Fredrik: " it don´t fits every solution/function, " IMo when programmer gets overly excited about possibilities language offers, he(or she) starts wasting development time developing overly complicated solutions he doesn't really need. I'd suggest to stick with KISS principle. – SigTerm Apr 06 '12 at 00:44
  • 1
    @Fredrik : To use Boost.Parameter, one need only do `#include ` -- a single file. Who cares what else _it_ includes? – ildjarn Apr 06 '12 at 00:46
  • 1
    @Fredrik: "My class is 200-250 lines of code" Which means you spent half of a day to make it. boost parameter is already written, tested debugged, bugs were found and fixed, and it is being maintained by other people. The big problem with your code that I'd need to look up your basic_parameter_class and ref to understand what exactly is going on here. Since you gratitously forgot to provide their implementation, your code will put maintainer into stupor for at least an hour. – SigTerm Apr 06 '12 at 00:48
  • I am aware of KISS, that was exactly why I wrote it. I find a place in my code there it fits, but of course it is only one place for now. But I see your point and all others for that matters :-) @ildjarn : Are you joking, open that file and you will find massive with includes... – Fredrik Apr 06 '12 at 00:55
  • @Fredrik : Hence the "who cares?". Why does it matter? (Hint: _it doesn't_.) – ildjarn Apr 06 '12 at 00:58
  • @ildjarn : Yes it really does, boost.parameter is a huge dependency for me, and for me unnecessary. – Fredrik Apr 06 '12 at 01:05
  • @Fredrik : How can a header-only library be a huge dependency? This is nonsense. – ildjarn Apr 06 '12 at 01:23
  • 1
    @Fredrik: Your class is opposite of KISS, because you decided to reinvent the wheel and use non-intuitive syntax instead of using existing solution that is maintained by skilled C++ developers for free (zero cost for you). See my answer for details. – SigTerm Apr 06 '12 at 01:32
  • 2
    @ildjarn: If you "indicate" that "header-only" always is "small" are you wrong, you can put massive with code inside header files, but it does´t mean you should. My goal is to have only STL dependency, with some exception. But this is off topic. SigTerm : I know, but actually have I tried it once, but it went not very well, didn´t understand it and I got "hard" compile errors, but it was a time ago. I am probably a better programmer today. – Fredrik Apr 06 '12 at 01:47
  • @Fredrik : I never said small, I said irrelevant. There's no linking involved, so there's no _problem_ (or at the least, you haven't actually said what it is). – ildjarn Apr 06 '12 at 02:56
  • Please move extended discussions to [chat]. – Tim Post Apr 06 '12 at 07:42

2 Answers2

59

My question is why this syntax is not used more, overloading operator,() to implement named parameters.

Because it is counter-intuitive, non-human-readable, and arguably a bad programming practice. Unless you want to sabotage the codebase, avoid doing that.

test(( name("Special :-)"), ref<unsigned long>(num) ));

Let's say I see this code fragment for the first time. My thought process goes like this:

  1. At a first glance it looks like an example of "the most vexing parse" because you use double-parentheses. So I assume that test is a variable, and have to wonder if you forgot to write variable's type. Then it occurs to me that this thing actually compiles. After that I have to wonder if this is an instance of an immediately destroyed class of type test and you use lowercase names for all class types.
  2. Then I discover it is actually a function call. Great.
  3. The code fragment now looks like a function call with two arguments.
  4. Now it becomes obvious to me that this can't be a function call with two arguments, because you used double parentheses.
  5. So, NOW I have to figure what the heck is going on within ().
  6. I remember that there is a comma operator (which I haven't ever seen in real C++ code during the last 5 years) which discards the previous argument. SO NOW I have to wonder what is that useful side effect of name(), and what the name() is - a function call or a type (because you don't use uppercase/lowercase letters to distinguish between class/function (i.e. Test is a class, but test is a function), and you don't have C prefixes).
  7. After looking up name in the source code, I discover that it is class. And that it overloads the , operator, so it actually doesn't discard the first argument anymore.

See how much time is wasted here? Frankly, writing something like that can get you into trouble, because you use language features to make your code look like something that is different from what your code actually does (you make a function call with one argument look like it has two arguments or that it is a variadic function). Which is a bad programming practice that is roughly equivalent to overloading operator+ to perform substractions instead of additions.

Now, let's consider a QString example.

 QString status = QString("Processing file %1 of %2: %3").arg(i).arg(total).arg(fileName);

Let's say I see it for the first time in my life. That's how my thought process goes:

  1. There is a variable status of type QString.
  2. It is initialized from a temporary variable of type QString().
  3. ... after QString::arg method is called. (I know it is a method).
  4. I look up .arg in the documentation to see what it does, and discover that it replaces %1-style entries and returns QString&. So the chain of .arg() calls instantly makes sense. Please note that something like QString::arg can be templated, and you'll be able to call it for different argument types without manually specifying the type of argument in <>.
  5. That code fragment now makes sense, so I move on to another fragment.

looks very more "modern" C++

"New and shiny" sometimes means "buggy and broken" (slackware linux was built on a somewhat similar idea). It is irrelevant if your code looks modern. It should be human-readable, it should do what it is intended to do, and you should waste the minimum possible amount of time in writing it. I.e. you should (personal recommendation) aim to "implement a maximum amount of functionality in a minimum amount of time at a minimum cost (includes maintenance)", but receive the maximum reward for doing it. Also it makes sense to follow KISS principle.

Your "modern" syntax does not reduce development cost, does not reduce development time, and increases maintenance cost (counter-intuitive). As a result, this syntax should be avoided.

quant
  • 21,507
  • 32
  • 115
  • 211
SigTerm
  • 26,089
  • 6
  • 66
  • 115
  • 3
    This is a very good answer that clearly demonstrates the severe maintenance nightmare that the asker’s "trick" would forevermore make for every person who ever has to look at something that's so confusing as to be misleading. Well done: +1. – tchrist Jul 22 '15 at 12:39
  • 2
    A large part of your argument comes down to a chicken and egg problem - people don't use it because it's not immediately obvious what it means, but it's not immediately obvious what it means because it's not used often. I imagine there are better reasons to avoid it than having people not understand it immediately if they haven't seen something similar before (if this were in itself a sufficient reason, I imagine new languages / language constructs would be extremely rare). – Bernhard Barker Jul 22 '15 at 12:52
  • `returns QString&` you can do that on xvalues ?? – v.oddou Apr 13 '18 at 04:41
3

There is not necessity. Your dynamic dispatch (behave differently, depending on the logical type of the argument) can be implemented a) much easier and b) much faster using template specialisation.

And if you actually require a distinction based on information that is only available on runtime, I'd try to move your test function to be a virtual method of the param type and simply use dynamic binding (that's what it's for, and that's what you're kind of reinventing).

The only cases where this approach would be more useful may be multiple-dispatch scenarios, where you want to reduce code and can find some similarity patterns.

tchrist
  • 78,834
  • 30
  • 123
  • 180
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • Thanks for answer, but define "much easier", for me is it so simple as it can get, almost as an simple function. The speed is also not so much slower, because the everything is saved in contiguous memory, often 100% on the stack. – Fredrik Apr 06 '12 at 00:23
  • 2
    @Fredrik: Your use of the sequence operator `,` is unusual and everything else than straightforward. Operator overloading is extremely tempting to misuse, and this is a misuse. In that regard it is the opposite of simple *to use*. Also I imagine that you have some magic in your parameter types going on that is not trivial, so its not simple *to implement*. What I meant with slower was; you have all these branching instructions that processors hate so much, that's bad. With templates you don't, instead you have lots of optimisation opportunities which this approach lacks, if I got it right. – bitmask Apr 06 '12 at 00:29
  • Also, note that "slow" is a relative term. If it helps your readability/maintainability don't give a rat's ass about efficiency (well, perhaps some tiny asses, perhaps mice's, depending on your project). Having said that; If an operation is a tiny bit slower that could *add up* incredibly, if that operation is done very very often (example: if you're implementing a [DES](http://en.wikipedia.org/wiki/Discrete_event_simulation), you wont even notice slow CLI-args parsing, but your scheduler should be fast as hell). – bitmask Apr 06 '12 at 00:34