2

I am puzzled, I was designing a template and I found a strange behavior regarding instantiating the template with T=float&:

// Given an available float f:
float f = 1.0;

// This getter works for T=float&:
template <typename T>
struct test {
  const T get() { return f; }
};

int main() {
  float& f1 = test<float&>().get();
}

The first strange thing is that f1 should be const float& for the code to be correct, and thus, I would expect an error, but it works fine.

The second strange thing is on this similar example that reports an error when I would expect it not to:

// Given an available float f:
float f = 1.0;

struct State {
  const float& get() { return f; }
};

// This does not work for T=float&:
template <typename T>
struct test2 {
  State state;
  const T get() { return state.get(); }
};

int main() {
  const float& f2 = test2<float&>().get();
}

The error reported is this:

main.cpp: In instantiation of 'const T test2<T>::get() [with T = float&]':
main.cpp:31:41:   required from here
main.cpp:22:36: error: binding reference of type 'float&' to 'const float' discards qualifiers
   const T get() { return state.get(); }

Which is strange, since the second example only declared const float& types, not float& and not const float, so I don't know what is happening.

Maybe templates were not designed to work with references or its a bug on GCC or I am just doing something dumb.

I tested this code using gcc (GCC) 6.3.1 20170306 and also on repl.it website using C++11.

Also if its a bug, I would be interested in any available work arounds.

VinGarcia
  • 1,015
  • 12
  • 19
  • 1
    Actually I wouldn't expect to get a `const float&` since that's a reference to a const float. When you substitute `T=float&` into `const T` you get `const (float&)` except that I don't think you can actually form a const reference (distinct from a reference to const) – SirGuy Jun 02 '17 at 17:45
  • Uhm, makes sense. So it is not possible to make the function return a `const float&` by feeding it with `T=float&`? Is there a way to simulate that behavior? For the users of my template it would make sense to have it to return sometimes `float` and other times `float&`. The alternative would be to create a second template with a different name to cover both cases. – VinGarcia Jun 02 '17 at 18:01
  • 1
    `const typename std::remove_reference::type&`, perhaps – Igor Tandetnik Jun 02 '17 at 18:17
  • Uhm, it's a nice trick but then the user would not be able to make the function return only `const float` by passing `T=float`. I might end up writing a second template for this case after all. – VinGarcia Jun 02 '17 at 18:28

3 Answers3

2

Just be more specialized. I think this gives the behavior you want:

template <typename T>
struct test {
    T get() { return f; }
};

template <typename T>
struct test<T&> {
    const T& get() { return f; }
};

To test:

int main() {      
    const float& f1 = test<float&>().get();
    float& f2 = test<float&>().get(); //Error
    const float& f3 = test<const float&>().get();
    float f4 = std::move(test<float>().get());
}
Yang Yang
  • 411
  • 3
  • 11
  • Nice, it took me some time to understand the specialization but it does exactly what I wanted. Just to confirm if I interpreted right: The specialization will be used only when the argument is a reference, and otherwise it will use the non-specialized version right? – VinGarcia Jun 02 '17 at 19:45
  • @VinGarcia Correct. – Yang Yang Jun 02 '17 at 20:41
1

Please take a look at this question and this question.

The same thing happens in your cases.

For the first case, our type is float&. And in float & const, the const is redundant. So it will be resolve as float & only.

For the second case, what returned by State::get() is a reference to a const float value. And the one returned by test2<float&>::get() still is float&. Now compiler will prevent you from assigning const float to a non-const one.

  • You mean "const float value" instead of "const integer value", but I got it, seems correct. Do you know of any idiom to make a function inside a template to return `const float&` or `const float` based on the template argument type? I am thinking or removing the `const` word from inside the template and forcing the user to use type `const float&` or `const float` whenever he wanted to specify the type. – VinGarcia Jun 02 '17 at 18:26
  • Yes, i edited the answer. For the idiom, I think @Even young gave the answer you want. – Hải Phạm Lê Jun 02 '17 at 23:29
1

For some completeness, here's the solution I would probably go with:

template <class T>
struct make_non_modifiable { using type = const T; };
template <class T>
struct make_non_modifiable<T&> { using type = const T &; };
template <class T>
struct make_non_modifiable<T*> { using type = const T *; };

template <typename T>
struct test {
  typename make_non_modifiable<T>::type get() { return f; }
};

I didn't try compiling this code so there might be a typo or two.
I usually prefer class template partial specializations over function template specializations because the latter is harder to understand alongside function overloading.

SirGuy
  • 10,660
  • 2
  • 36
  • 66