Saturday, 17 May 2014

Named function arguments for C++, again

This is probably/maybe the final installment on my inoffical series on emulating named function parameters in C++ (see here and here). This time, I was inspired by the technique I spotted in the sqlpp11 library (see also the talk here or the slides here).

Want to see it in action? You can call functions like that:
// 2 string parameters
test_func(first_name = "alfa", second_name = "beta");
      
// a string and a bool parameter
test_func_bool("zeta", enable_widgets = true);  

//test_func("alfa"); -- too much, not supported!  
The downside is, that you always must use the names of parameters, i.e. you cannot fall back to positional usage. This is intentional, as to keep the implementation as simple as possible. It's doable, of course, but for what reason? If you don't need name parameter at a given position, don't use it.

So how does the implementation work? It's rather minimal, as ot doesn't try to do all the thing possible. Call me non-genius, but such small code pleases me. Basically, the name argument instance will return an asignment proxy, which remembers the value assigned to it. You say it's unnecessary? And you are totally right, it's only there as to tie a name to the given parameter!
template<typename Lhs, typename Rhs>
struct assignment_t
{
  using lhs_t = Lhs;
  using rhs_t = Rhs;
  lhs_t _lhs;
  rhs_t _rhs;   
};
 
template<typename T>
struct named_arg_t
{
  using type = assignment_t<named_arg_t, T>;
  auto operator=(T arg)
    -> assignment_t<named_arg_t, T>
  {
    return { {}, arg };
  }    
};
try it online: http://www.compileonline.com, you can find the complete code here.

How do you define functions using the named parameters?
// usage string:
named_arg_t<std::string> first_name{};
named_arg_t<std::string> second_name{};
 
void test_func(named_arg_t<std::string>::type first_name, named_arg_t<std::string>::type second_name) 
{
  auto firstname_val = first_name._rhs;
  auto secondname_val = second_name._rhs;
  std::cout << "first name=" << firstname_val 
            << ", second name=" << secondname_val 
            << std::endl;
}
 
// usage bool:
named_arg_t<bool> enable_widgets{};
 
void test_func_bool(const std::string& name, named_arg_t<bool>::type enable_widgets)
{
  if(enable_widgets._rhs)    
  {
    std::cout << "enable widgets for: " << name << std::endl; 
  }
}
You see, there's another caveat - the parameters are still positional, you cannot call the function like that:
// 2 string parameters
test_func(second_name = "beta", first_name = "alfa");
because C++ knows only positional pameters.

I think, this usage is intuitive - you express your wish to have a named parameter (name_arg_t<>) of a gven type (string or bool). Then you acces the parameter's value using a getter in the function - I think it's a minor inconvenience and quite understandable. The naming of the parameter's value is due to discussion, here it documents the implementation mechanism, which admittedly could be not the best solution fo a day to day usage.

But you say - WTF? You say - use Boost.Parameter! Why did I spend my time on those blogpost if there' a solution already? The reason is that in many project you either can't use it (company guidelines), or you wouldn't like to use it (already a big, do-it-all library in place). Another reason is that "small is beautiful"!

2 comments:

  1. If you make both arguments different static types (for example by adding an unused, int template parameter), at least the compiler should complain, when you get the parameters wrong on the calling side.

    Cheers,
    Torsten

    ReplyDelete
  2. @Torsten
    yes, that would be a nice feature!

    ReplyDelete