6

I want to pass a parameter(s) (of some concrete type, say int) to the member function by r- or l- value (const) reference. My solution is:

#include <type_traits>
#include <utility>

struct F
{
    using desired_parameter_type = int;

    template< typename X, typename = typename std::enable_if< std::is_same< typename std::decay< X >::type, desired_parameter_type >::value >::type >
    void operator () (X && x) const
    {
        // or even static_assert(std::is_same< typename std::decay< X >::type, desired_parameter_type >::value, "");
        std::forward< X >(x); // something useful
    }
};

Another exaple is here http://pastebin.com/9kgHmsVC.

But it is too verbose. How to do it in a simpler way?

Maybe I should use the superposition of std::remove_reference and std::remove_const instead of std::decay, but there is just a simplification here.

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • There's no point passing a simple type like `int` by rvalue. In the more general case, I believe C++11 has a special case for this, where an rvalue parameter will be read as being an rvalue *or* lvalue parameter. I can't remember the exact situation where that applies though. – Dave May 04 '13 at 16:38
  • So you want `x` to be either an rvalue reference (if an rvalue reference is passed) or an lvalue reference to `const`? Am I understanding correctly, that `X&&` would not be acceptable to you because it would be an lvalue reference to non-`const` when an lvalue is passed? – Andy Prowl May 04 '13 at 16:41
  • Ah here we go, I believe you're trying to do this: http://thbecker.net/articles/rvalue_references/section_07.html which is explained using rvalues in the next page. Yell if I misunderstood the question. – Dave May 04 '13 at 16:41
  • @AndyProwl my problem is how to provide the parameter of some _concrete_ type but with unspecific "referenceness". – Tomilov Anatoliy May 04 '13 at 16:43
  • @Dukales: I can't follow you. If you just do `template void operator () (X&& x)` you *will* be allowed to pass any parameters (lvalues or rvalues). Given an argument of type `U`, type deduction will make `x` a `U&& x` in case the argument is an rvalue, and a `U& x` in case the argument is an lvalue – Andy Prowl May 04 '13 at 16:44
  • @AndyProwl you understand my wishes correctly. – Tomilov Anatoliy May 04 '13 at 16:47
  • @Dukales: I tried to answer. Unless I am missing something obvious, you just need an lvalue reference to `const`. – Andy Prowl May 04 '13 at 16:52

3 Answers3

4

If I understand your question correctly, you wish to have a single function whose parameter is either an rvalue reference (in case an rvalue is provided) or an lvalue reference to const (in case an lvalue is provided).

But what would this function do? Well, since it must be able to handle both cases, including the case where an lvalue is provided, it cannot modify its input (at least not the one bound to the x parameter) - if it did, it would violate the semantics of the const reference.

But then again, if it cannot alter the state of the parameter, there is no reason for allowing an rvalue reference: rather let x be an lvalue-reference to const all the time. lvalue references to const can bind to rvalues, so you will be allowed to pass both rvalues and lvalues.

If the semantics of the function is different based on what is passed, then I would say it makes more sense to write two such functions: one that takes an rvalue reference and one that takes an lvalue reference to const.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • constness is optional (so all of `&&`, `&`, `const &` allowed) – Tomilov Anatoliy May 04 '13 at 16:53
  • 1
    @Dukales: I do not understand :( What do you mean? If the function has to be able to work with `const` objects, it means it won't try to modify `x`. But then again, this means you do not need an rvalue reference – Andy Prowl May 04 '13 at 16:54
  • Do you know about metaprogramming? Lets assume, that functions, which are passed parameters to, will determine what to do with (how to dispatch) their particular "referenceness" and constness. – Tomilov Anatoliy May 04 '13 at 16:59
  • @Dukales: I know about metaprogramming, yes. But I would be curious to see how do you imagine such a function to work. Suppose you are given a way to write the signature of the function as you wish, what do you expect to write inside the *body* of such a function? – Andy Prowl May 04 '13 at 17:03
  • I want to write an initialization list inside the constructor. – Tomilov Anatoliy May 04 '13 at 17:09
  • I terms of _design by contract_ I want to have all the restrictions localized in constructor prototype directly. – Tomilov Anatoliy May 04 '13 at 17:10
  • @Dukales: I'm sorry, I do not understand. Perhaps an example would help. – Andy Prowl May 04 '13 at 17:11
  • I am powerless over the language barrier. It's such a pain to express technical ideas. I'd rather wait. Not blame me for it. – Tomilov Anatoliy May 04 '13 at 17:14
  • @Dukales: Just write a piece of code of how the body of your function would look like. If it is a constructor, just write a class with that constructor and leave the signature empty. My feeling is that this is hard to express because of some misconception you are having or incorrect assumptions you are making. Usually, writing it down in formal terms helps spotting them and clarifying them. Or perhaps I am failing to see something, and in that case the concrete example would help both me and you. Or if you prefer waiting, feel free :) Maybe somebody with more insight than me will answer – Andy Prowl May 04 '13 at 17:17
  • @Dukales: OK, so what is wrong if you just use `T0&&`, `T1&&`, and `T2&&` without any special SFINAE constraint? – Andy Prowl May 04 '13 at 17:36
  • In this case, the readability will not be observed. I want to impose restrictions on the type of value passed. – Tomilov Anatoliy May 04 '13 at 17:43
  • @Dukales: I don't understand. What is the line that you would like to behave differently than it does if you just use `T0&&`, `T1&&`, and `T2&&`? – Andy Prowl May 04 '13 at 17:44
  • In this case user of my library not know about type's restrictions. – Tomilov Anatoliy May 04 '13 at 17:46
  • *In example I forgot to specify the `std::forward< T? >`. I corrected. – Tomilov Anatoliy May 04 '13 at 17:53
1

As Andy has mentioned, the important thing here is what you could actually do inside your function which would make sense.

  • You can forward arguments to another function. In this case, using a template doesn't matter, because if the wrong parameter type is given it will still produce a compile error (wrong types sent to second function). You can use template <typename T> blah (T && x).
  • Anything else would require you to write different code depending on whether it is an r- or l- value reference, so you'll need to write 2 functions anyway: blah (const int & x) and blah (int && x).

I assume you must be attempting the first option and you're trying to make any potential compiler errors more user-friendly. Well, I'd say it's not worth it; the programmer will still see a "called by…" list in the output of any decent compiler.

Dave
  • 44,275
  • 12
  • 65
  • 105
1

Actually, this is a very good question. So far I have also been using the universal reference trick plus the enable_if hammer. Here I present a solution that doesn't make use of templates and uses lvalue cast as an alternative.

What follows is a real example where the situation arises using the known example of inplace ofstream usage that is not possible (or very hard) in C++98 (I use ostringstream in the example to make it more clear).

First you will see a function on lvalue reference as often seen in C++98.

#include<iostream>
#include<sstream>
struct A{int impl_;};

std::ostringstream& operator<<(std::ostringstream& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // possibly much longer code.
    return oss;
}
// naive C++11 rvalue overload without using templates
std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // ok, but there is code repetition.
    return oss;
}

int main() {

    A a{2};
    {// C++98 way
        std::ostringstream oss;
        oss << a;
        std::cout << oss.str() << std::endl; // prints "A(2)", ok"
    }
    {// possible with C++11, because of the rvalue overload
        std::cout << (std::ostringstream() << a).str() << std::endl; //prints "A(2)", ok
    }
}

As you can see in C++11 we can achieve what we can't in C++98. That is to make use of the ostringstream (or ofstream) inplace. Now comes the OP question, the two overloads look very similar, can both be joined in one?

One option is to use universal reference (Ostream&&), and optionally with enable_if to constrain the type. Not very elegant.

What I found by using this "real world" example is that if want to use the same code for lvalue ref and rvalue ref is because probably you can convert one to the other!

std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    return operator<<(oss, a);
}

This looks like an infinitely recursive function, but it is not because oss is an lvalue reference (yes, it is an lvalue reference because it has a name). So it will call the other overload.

You still have to write two functions but one has a code that you don't have to maintain.

In summary, if "it makes sense"© to apply a function both to a (non const) lvalue reference and rvalue that also means that you can convert the rvalue into an lvalue and therefore you forward to a single function. Note that the "it makes sense" depends in the context and the meaning of the intended code, and it is something that we have to "tell" the compiler by explictly calling the lvalue overload.

I am not saying that this is better than using universal reference, I say it is an alternative and arguably the intention is more clear.

Editable code here: http://ideone.com/XSxsvY. (Feedback is welcomed)

alfC
  • 14,261
  • 4
  • 67
  • 118