2

I want to be able to supply functions which take an existing object of a given type by const reference.

i.e. I want by-const-ref-but-no-temporaries-created. [edited this phrase to clarify it!]

I can't think of any way to express that in C++ directly:

1 void fn(const T & t)
will create a temporary T from an S if a (non-explicit) T(S) exists.

2 void fn(T & t)
will only allow a non-const t as an argument (I need this to work for const t's as well).

3 void fn(T && t)
requires a non-const rvalue.

4 void fn(const T && t)
requires a const rvalue

Is there any way to do this - accept any argument by reference, const, without allowing a temp to be created?


Failed thoughts:

Supplying #2 and #4 without supply the other two, and now we have an unambiguous fn that takes a reference to any existing T (but won't silently generate a temporary)?

Nope, that isn't adequate either, because I need to also be able to bind to a const T & - Where the target object is itself been passed in by const reference to our caller...


I guess I see no way to achieve this except in the case where I can control T to ensure it doesn't supply an implicit T(S).

Am I right, or am I overlooking some solution?

Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • 2
    `void fn(const T & t)` + `void fn(const T && t) = delete` should do the trick. Alternatively put a `static_assert` there to explain to people why they have to give the temporary that they want to pass in a name. – nwp Mar 30 '17 at 15:25
  • 1
    Why would you want to disallow temporaries anyway? – Christian Hackl Mar 30 '17 at 15:29
  • What do you want to do in your function? – Sniper Mar 30 '17 at 15:36
  • Our code base extensively uses MFC's CString - which have CString(const char-type *) non-explicit ctors. This makes it trivial to accidentally have functions which take `const CString &` actually create a temporary CString on the spot - which can be insanely poor performance (when you notice it), or just a drag when you don't. So I want various free fn's which take CString's if they're already a CString, but plz don't create a temp if they aren't (generate an error!) – Mordachai Mar 30 '17 at 15:42
  • FWIW, these are in fact free functions in my actual use case. – Mordachai Mar 30 '17 at 16:02
  • 1
    Below you wrote: "I'm only trying to avoid fn(t) silently converting a non-t to a t". This is completely different to your actual question, which is all about rvalues and lvalues (all of type `T`). Maybe you need to do some clarifying. – Nir Friedman Mar 30 '17 at 16:08
  • 1
    You should look into replacing `const CString &` with something like [`std::string_view`](http://en.cppreference.com/w/cpp/string/basic_string_view). – nwp Mar 30 '17 at 16:10
  • Sorry for the confusing question - it was my fundamental confusion (and hence need to ask!) ;) – Mordachai Mar 31 '17 at 15:52

2 Answers2

3

If this is a free function then you can do as nwp suggests and add an overload taking a rvalue reference and deleting it.

void fn(const SomeType& t)
{
    // stuff to do with const lvalue
}

void fn(SomeType&&) = delete; // compiler error if you give me an rvalue

This works because a rvalue reference is preferred over a reference to const. So when overload resolution kicks in and you pass a temporary the compiler will select void fn(const T&&) as the best match. After that it will see that the function is deleted and it will issue a compiler error

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Ugh! I wish this worked for all of my actual use-cases! So close! But, in my case, I also want to allow this: `fn(fm())` - where `fm()` returns a `T` - but in this circumstance, it is a `const rvalue` but the choice to generate a `const rvalue` was explicitly reasonable in the code. I'm only trying to avoid `fn(t)` silently converting a non-`t` to a `t`... :( – Mordachai Mar 30 '17 at 16:01
  • 1
    @Mordachai Maybe [this answer](http://stackoverflow.com/a/43122779/4342498) I just wrote on a related question could help. I'm not sure though on how exactly to tie them together. I'll need to think about this some more. – NathanOliver Mar 30 '17 at 16:04
  • 1
    @Mordachai I also had a bug in the code. It should have been `void fn(SomeType&&) = delete;` and not `void fn(const SomeType&&) = delete;`. – NathanOliver Mar 30 '17 at 16:07
  • Even the corrected non-const rvalue version of your solution fails for me due to my needing to allow `f(g())` where `g()` returns a `T` (in this Q's parlance). Hence, in that case, an rvalue is perfectly reasonable - it isn't being induced - it is already there by-definition, explicitly. I do wonder if there is some template magic to distinguish such a case - all non-`T`'s from actual `T`'s... maybe that is a fruitful thought.... – Mordachai Mar 30 '17 at 16:16
3

Judging from the comments, it seems like your actual question has nothing to do with rvalues and lvalues, but simply preventing implicit conversions. You can do this by changing the function to a function template, and then constraining the template with sfinae:

template <class T, std::enable_if_t<std::is_same<std::decay_t<T>, CString>::value, int> = 0>
void fn(const T&) {

}

This will only work if called with some kind of CString, a type that implicit converts to a CString will not work.

Live example: http://coliru.stacked-crooked.com/a/80602c39cdc4d35e

That said, a better solution would simply be to change those functions to accept a string_view, which can be cheaply implicitly constructed from various kinds of strings. I don't know if it handles CString or not, but you can always right your own. It's a fairly simple class to write. http://en.cppreference.com/w/cpp/string/basic_string_view

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • Indeed - I was flailing for an answer without knowing where to look. SFINAE sounds like a promising approach. – Mordachai Mar 30 '17 at 16:38
  • I've been waiting what feels like centuries for a good string_view and ranges - but I have not had the time to see if they're actually good enough for my use yet? I'll definitely take a look (and writing adaptors for `CString`s is no problem) – Mordachai Mar 30 '17 at 16:41
  • @Mordachai There may be some annoyances because you basically lose any member functions of `CString`, if it's like `std::string` then that's a lot. But typically you can do something similar with standard algorithms or a for loop. Other than that though it's pretty trivial; a string view is just two pointers, one to the beginning, one to the end. Some const-correct begin and end members. And a few implicit constructors. You can write something good enough for your use in probably 100 lines of code. – Nir Friedman Mar 30 '17 at 18:07
  • thanks for the info - yeah, I've had the idea of a string view forever - but I desperately want std::range to become a thing so that it can be done correctly and not as a hackish thing. The entire standard library should have been written with views (ranges) from the very start - and then rich range-adaptors / view-adaptors. As it is, it's pretty dumb (but getting better). In my case here - even a view is dumber than what I want - which is "compiler - when you see get_length(CString), this is the **most efficient way to resolve that** – Mordachai Mar 30 '17 at 19:16
  • @Mordachai To be clear, you don't need ranges in any way to have a nice solution for this. `string_view` will be standardized without ranges. Ranges are mostly just necessary for nice composition which isn't what's going on here. – Nir Friedman Mar 30 '17 at 19:53