0
votes

I have some code that uses two different type of colours, 8 bit per channel and 16 bit per channel, each represented by a struct. In order to effectively reuse my code I have a template function that does some rendering with them. I would therefore like a templated function to grab the max value of a channel of my colours.

My initial attempt looked like this. I have only shown the specialization for 8 bpc

struct Pixel8
{
   unsigned char r;
   unsigned char g;
   unsigned char b;
};
#define PIXEL8_MAX 255

template <class PIXEL>
auto getMax( ) -> decltype( PIXEL::r )
{
   static_assert( sizeof(PIXEL) > 0, "getMax can only be called with a pixel type." );
}
template <>
auto getMax<Pixel8>( ) -> decltype( Pixel8::r )
{
   return PIXEL8_MAX;
}

This would not compile with Visual studio 2012. I get the error

1> error C2785: ''unknown-type' getMax(void)' and 'char getMax(void)' have different return types 1> see declaration of 'getMax'

To me I feel that this should work but I have been unable to find any examples. There is one other question similar at Specialize function template with decltype trailing return type, but here the return type is the same for each specialization.

I have found a workaround which I will post as an answer so that others can benefit. However it is not very transparent so if someone can tell me if the code above is valid and this is a VC++ incompatibility or if it is not valid then why and how I can make it valid?

3
BTW, 255 doesn't fit in signed char (but does in unsigned char). char may be signed.Jarod42
That static_assert will always fire on some compilers because it doesn't depend on template parameters.TartanLlama
I think you should look at traits instead of plain functions.Telokis
@Jarod42, thanks I have edited the questionPhil Rosenberg
@TartanLlama Okay, I hadn't realised that. I guess that is because the compiler sees no dependence upon the template so compiles it as a non-template function or something? I have edited adding sizeof(T) >0 which I saw elsewhere but didn't appreciate the need for.Phil Rosenberg

3 Answers

0
votes

Try to make the return type depend on the template parameter type:

struct Pixel8
{
    char r;
    char g;
    char b;
};

template<typename T>
struct ColourType
{
    typedef decltype(T::r) type;
};

#define PIXEL8_MAX 255

template <class PIXEL>
typename ColourType<PIXEL>::type getMax()
{
    static_assert(false, "getMax can only be called with a pixel type.");
}
template <>
ColourType<Pixel8>::type getMax<Pixel8>()
{
    return PIXEL8_MAX;
}
0
votes

This is a workaround for getting the required behaviour. It relies on the fact that there is another way to define a type in C++ without using decltype. This is to use typname.

It's main use in this context is as follows, imagine two classes and two functions

class MyClass
{
public:
    class MyThing
    {
    };
};

class MyOtherClass
{
public:
    static int MyThing;
}

template< class T >
void func1( T something )
{
    typename T::MyThing thing;
}

template< class T >
void func2( T something )
{
    T::MyThing = 5;
}

If we pass either class as a template parameter T, then T::MyThing would be a type for MyClass and a static int for MyOtherClass. These are entirely incompatible, so we use typename to separate them. In func1 we use typename to state that T::MyThing is a type. We could pass in a MyClass object. In func2 we omit typename and therefore T::MyThing is interpreted as a variable and we could pass in a MyOtherClass. Without typename, there would be no way to tell if T::MyThing was a type or a static variable.

Note also that typename can refer to a typdef as well as an internal class, So if we create a templated class or struct which includes a typedef for a type we can access that type using typename.

template<class PIXEL>
struct pixTypes
{
};

template<>
struct pixTypes<Pixel8>
{
    typedef char type;
};

template <class PIXEL>
auto getMax( ) -> typename pixTypes<PIXEL>::type
{
    static_assert( false, "getMax can only be called with a pixel type." );
}
template <>
auto getMax<Pixel8>() -> typename pixTypes<Pixel8>::type
{
    return PIXEL8_MAX;
}

So now we get our return type from a typename which refers to a typedef in a specialized templated struct.

This seems rather a convoluted way around everything, but it does compile on Visual Studio 12.

0
votes

Using macros to define constants, as in this question's code

#define PIXEL8_MAX 255

… is not ideal.

Also, the definition conflicts with the type used. A char is not guaranteed to have that maximum value, and with most implementations will by default not have that maximum value. You can define a Byte type as unsigned char, but even so you're not guaranteed 8 bits, and should check that.

The standard library provides the numeric_limits class template to deal with maximum values etc.:

#include <limits>       // std::numeric_limits

#define STATIC_ASSERT( e ) static_assert( e, #e )

using Byte = unsigned char;
int const bits_per_byte = std::numeric_limits<Byte>::digits;
STATIC_ASSERT( bits_per_byte == 8 );

struct Pixel8
{
    Byte r;
    Byte g;
    Byte b;
};

template< class Pixel >
constexpr auto getMax() -> decltype( Pixel::r )
{
    return std::numeric_limits<decltype( Pixel::r )>::max();
}

#include <iostream>
using namespace std;

auto main() -> int
{
    cout << +getMax<Pixel8>() << endl;
}