Forwarding Reference with default argument?

602 views Asked by At

I am having trouble trying to figure out how to specify default arguments for a Forwarding Reference (formerly called Universal References by Scott Meyers).

Here's an example of the code trying to do what I want to do:

struct encoder_t {

} const encoder = {};

struct validator_t {

} const validator = {};

struct test {

    template <typename Range, typename Encoding, typename Validation>
    test ( Range&& range, Encoding&& encoding = encoder, Validation&& validation = validator ) {

    }

};

int main() {

    test( "woof" );

}

Also available on Coliru.

Mucking through the errors, you find out that you can make it work by defaulting the template argument, and then default-constructing the argument thereafter:

// Works! But the syntax is strange... potential ramifications/deduction mishaps?
// Is this the "proper" way to default these arguments?
template <typename Range, typename Encoding = encoder_t, typename Validation = validator_t>
test ( Range&& range, Encoding&& encoding = Encoding(), Validation&& validation = Validation() ) {

}

Also on Coliru.

Is this the "proper" way to be handling this? What should be the syntax I use? Are there multiple ways to have the desired effect of "defaulted forwarded references"? Which way should I be writing this? Also keep in mind that later on I'll be sprinkling tons of SFINAE on the code, so I'd much more prefer something that does not include writing multiple overloads.

1

There are 1 answers

0
M.M On BEST ANSWER

First of all, template types cannot be deduced from default arguments. So we can only look for other ways to achieve the idea of being able to optionally specify an argument to match a forwarding reference.

This workaround suggests itself:

template <typename Range, typename Encoding = encoder_t, typename Validation = validator_t>
test ( Range&& range, Encoding&& encoding = encoder, Validation&& validation = validator )
{
}

however this fails: Forwarding References work by deducing the template type as a reference type, but you have specified an object type; and the rvalue reference now cannot bind to the lvalues of your dummy objects.

As you say in your post, you can fix this by making the defaults be a temporary object encoder_t{} instead of a dummy object. This question confirms that the reference does remain a Forwarding Reference in that case.

Another workaround would be to use separate constructors instead of default arguments:

template <typename Range>
test ( Range&& range )
{
}

template <typename Range, typename Encoding>
test ( Range&& range, Encoding&& encoding )
{
}

template <typename Range, typename Encoding, typename Validation>
test ( Range&& range, Encoding&& encoding, Validation&& validation )
{
}

Depending on exactly what you do in the constructor body, you could implement this by using constructor delegation.

Since you mention intent to add SFINAE, maybe this post will have some ideas: How to allow default construction when using universal reference in constructor