Ciaran McCreesh’s Blag

Now with 17% more caffeine

C++ Named Function Parameters

C++ doesn’t have named function parameters. In some ways this isn’t a huge deal, since the compiler will usually catch when you screw up the ordering of arguments to a function. But if you’ve got a function accepting multiple arguments of the same type, the compiler isn’t going to save you. So we want to allow something like following:

shop.populate(
    param::number_of_cheeses() = 0,
    param::number_of_parrots() = 1,
    param::parrot_variety() = "Norwegian Blue"
    );

We also want:

  • As little as possible boilerplate from the programmer.
  • Type safety. It shouldn’t compile if the arguments are wrong.
  • Zero overhead.

It would be nice to allow arguments to be specified in any order, and there is a way of doing that using C++0x, but it’s rather convoluted, so we’ll stick with the requirement that arguments be in the right order for now.

First, we want to work out the type of those param::foo() things. Since we’re using operator=, they need to be structs or constants of some kind (since operator= can only be overloaded as a member function). Since we want lots of them of different types, and since we don’t want to have to worry about declaring the same name multiple times (which means we’d start hitting the ODR), a typedef of a template seems in order. Thus, we’d like to do:

namespace params
{
    typedef Name</* something */> number_of_cheeses;
    typedef Name</* something */> number_of_parrots;
    typedef Name</* something */> parrot_variety;
}

As for the something, the best I’ve been able to come up with is an inline forward declaration of a meaningless struct:

namespace params
{
    typedef Name<struct N_number_of_cheeses> number_of_cheeses;
    typedef Name<struct N_number_of_parrots> number_of_parrots;
    typedef Name<struct N_parrot_variety> parrot_variety;
}

What about the function parameters?

void Shop::populate(
    const NamedValue<param::number_of_cheeses, int> & number_of_cheeses,
    const NamedValue<param::number_of_parrots, int> & number_of_parrots,
    const NamedValue<param::number_of_cheeses, std::string> & parrot_variety)
{
    /* ... */
}

There’s a small amount of duplication there, but that’s a necessity: it’s considered a useful feature of C and C++ that declarations and implementations of functions can use different names for parameters.

As for using the parameters, we’ve got two options. We could add a super magic cast operator to NamedValue, or we could make it explicit. Since super magic casts have a nasty habit of doing really weird things, we’ll make it explicit using operator():

void Shop::populate(
    const NamedValue<param::number_of_cheeses, int> & number_of_cheeses,
    const NamedValue<param::number_of_parrots, int> & number_of_parrots,
    const NamedValue<param::number_of_cheeses, std::string> & parrot_variety)
{
    cheeses.resize(number_of_cheeses());
    cage.insert(number_of_parrots(), parrot_variety());
}

Now we just have to make it work. First, NamedValue, remembering to provide const and non-const versions of our operator:

template <typename T_, typename V_>
class NamedValue
{
    private:
        V_ _value;

    public:
        explicit NamedValue(const V_ & v) :
            _value(v)
        {
        }

        V_ & operator() ()
        {
            return _value;
        }

        const V_ & operator() () const
        {
            return _value;
        }
};

Then Name. Our first attempt might look like this:

template <typename T_>
struct Name
{
    template <typename V_>
    NamedValue<Name<T_>, V_> operator= (const V_ & v) const
    {
        return NamedValue<Name<T_>, V_>(v);
    }
};

But there’s a problem: whilst this works for int and most classes, it does something immensely stupid when fed a string literal. We could require users to write out parameters like:

    param::parrot_variety() = std::string("Norwegian Blue")

but that’s rather silly. So instead we’ll add in a way of overriding types for NamedValue, keeping it nice and generic in case any similar situations crop up elsewhere:

template <typename T_>
struct NamedValueType
{
    typedef T_ Type;
};

template <int n>
struct NamedValueType<char [n]>
{
    typedef std::string Type;
};

template <typename T_>
struct Name
{
    template <typename V_>
    NamedValue<Name<T_>, typename NamedValueType<V_>::Type> operator= (const V_ & v) const
    {
        return NamedValue<Name<T_>, typename NamedValueType<V_>::Type>(v);
    }
};

Fortunately, g++ is smart enough to compile all of this into exactly the same code as it would if named parameters weren’t used.

And there we have it: very low boilerplate type safe named parameters with no icky macros.

8 responses to “C++ Named Function Parameters

  1. Anonymous May 21, 2010 at 5:39 am

    “Fortunately, g++ is smart enough to compile all of this into exactly the same code as it would if named parameters weren’t used.”

    You sure?

    • Ciaran McCreesh May 21, 2010 at 10:47 am

      For certain values of g++, compile and exactly the same, yes.

      Depending upon how you use it, there can be an extra copy in there, although that’s easily removed by a bit of tinkering.

  2. Łukasz Michalik May 21, 2010 at 12:57 pm

    What if you wanted to pass expensive-to-copy or polymorphic objects by reference?

    • Ciaran McCreesh May 21, 2010 at 1:00 pm

      Then you make use of tr1’s remove_reference and friends to make NamedValue work with references too. Not hard, but distracting from the main point.

  3. Joe May 21, 2010 at 1:33 pm

    Why not use boost?

    http://www.boost.org/doc/libs/1_43_0/libs/parameter/doc/html/index.html

    It’s been around for quite some time.

    • Ciaran McCreesh May 21, 2010 at 1:41 pm

      The Boost way of doing it is extremely convoluted, involving some rather horrible macros. BOOST_PARAMETER_FUNCTION looks nothing like a ‘regular’ function declaration… This way’s a lot simpler.

      Plus there’s the usual Boost can of worms that means it’s pretty much impossible to use it for a system-critical package manager, which is the one thing I really care about.

  4. Ben Craig May 21, 2010 at 1:37 pm

    Why not use the technique discussed in Design and Evolution of C++? That technique lets you reorder the parameters, and have default arguments for any of the parameters. It also involves less template magic.

    struct ArgBlock {
    int num_cheeses_;
    int num_parrots_;
    std::string parrot_variety_;
    ArgBlock &num_cheeses(int x) {num_cheeses_ = x; return *this;}
    ArgBlock &num_parrots(int x) {num_parrots_ = x; return *this;}
    ArgBlock &parrot_variety(const std::string &x) {parrot_variety_ = x; return *this;}
    };
    void Shop::populate(const ArgBlock &args);
    shop.populate(ArgBlock.
    parrot_variety("Norwegian Blue").
    number_of_parrots(1).
    number_of_cheeses(0));

    • Ciaran McCreesh May 21, 2010 at 1:43 pm

      That’s an awful lot of work, which means I’d just end up not using it very often, leading to more screwups. It also doesn’t provide a way of detecting missing or duplicated parameters at build time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.