3

In this answer what I really wanted to do is define a typename in my template parameters which could be used in the cast and return.

So this:

template <typename T>
auto caster(T value)
  -> typename std::enable_if<sizeof(unsigned char) == sizeof(T), unsigned char>::type
{ return reinterpret_cast<unsigned char&>(value); }

Would become this:

template <typename T, typename R = std::enable_if<sizeof(unsigned char) == sizeof(T), unsigned char>::type >
R caster(T value) { return reinterpret_cast<R&>(value); }

This works and behaves as desired for a single template specialization, but say that I add another overload:

template <typename T, typename R = std::enable_if<sizeof(short) == sizeof(T), short>::type>
R caster(T value) { return reinterpret_cast<R&>(value); }

Now I get an error:

error C2995: 'R caster(T)' : function template has already been defined

Is there a way to convince the compiler that only one of these specializations will actually build for any given call?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

2 Answers2

1

No, there isn't. Template default arguments are just that, defaults. Any user could call caster<short, short>, which would match both overloads.

However, it would be possible to add more dummy arguments.

template <typename T,
          typename R = typename std::enable_if<sizeof(unsigned char) == sizeof(T), unsigned char>::type >
R caster(T value) { return reinterpret_cast<R&>(value); }

template <typename T,
          typename R = typename std::enable_if<sizeof(short) == sizeof(T), short>::type,
          typename = void>
R caster(T value) { return reinterpret_cast<R&>(value); }

(Also note the added typename.)


However, since all bodies are identical, I probably wouldn't go with overloads.

template <std::size_t N>
struct cast_result;

template <>
struct cast_result<sizeof(std::uint8_t)> {
  typedef std::uint8_t type;
};

template <>
struct cast_result<sizeof(std::uint16_t)> {
  typedef std::uint16_t type;
};

...

template <typename T, typename R = typename cast_result<sizeof(T)>::type>
R caster(T value) {
  return reinterpret_cast<R&>(value);
}

A final note: this use of reinterpret_cast is a violation of the aliasing rules. However, that's easily fixed:

template <typename T, typename R = typename cast_result<sizeof(T)>::type>
R caster(T value) {
  R result;
  std::memcpy(&result, &value, sizeof result);
  return result;
}
  • Ah, well at least I understand why it's not building now. Seems like I could setup a `typedef` or something that wouldn't be a user parameter, but would define the type for use. – Jonathan Mee Feb 23 '15 at 13:56
  • I'm not seeing any errors on the `reinterpret_cast`? It certainly makes sense to me that it should work as-is. If the bit count is the same there shouldn't be a problem with treating one as the other? – Jonathan Mee Feb 23 '15 at 20:54
  • @JonathanMee The language could have been defined that way, but wasn't. The standard contains: "If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:" followed by a list that does not contain any notes on objects of a particular size. Some compilers (like GCC) use that rule to aggressively optimise code, noting that e.g. given `int *a` and `long *b`, code that stores to `*a` and reads from `*b`, the compiler can re-order the accesses since the pointers are not allowed to point to the same object. –  Feb 23 '15 at 21:07
  • @JonathanMee If you compile with `gcc -O2 -Wall`, you'll get a warning about it: "dereferencing type-punned pointer will break strict-aliasing rules". (Note: that warning is trivially suppressed, but don't be fooled into thinking that code that does cause that warning is free of aliasing violations.) –  Feb 23 '15 at 21:08
0

It seems that the best solution here may be to use a slew of conditionals, which would prevent me from having to fool with template specializations:

template <typename T, typename R = std::conditional<sizeof(T) == sizeof(unsigned char),
                                                    unsigned char,
                                                    conditional<sizeof(T) == sizeof(unsigned short),
                                                                unsigned short,
                                                                conditional<sizeof(T) == sizeof(unsigned long),
                                                                            unsigned long,
                                                                            enable_if<sizeof(T) == sizeof(unsigned long long), unsigned long long>::type>::type>::type>::type>
R caster(T value){ return reinterpret_cast<R&>(value); }

My apologies to the reader cause it's like reading nested ternaries. However I'm currently unaware of a cleaner way to handle this.

This sadly still doesn't prevent the user from stomping on all my defaulting by providing his own second template parameter as mentioned by hvd.

EDIT:

I've asked another question here which has a solution that doesn't require placing the typename in the template definition and doesn't require declaring the type twice.

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288