halivingston halivingston - 2 months ago 11
C++ Question

C++ template specialization not working when comparing sizeof(type) == constant

#include <cstdint>
#include <iostream>

uint32_t ReadInt() { return 0; }
uint64_t ReadLong() { return 0; }

template<class SizeType>
class MyType
{
public:
SizeType Field;
MyType()
{
Field = sizeof(SizeType) == 8 ? ReadLong() : ReadInt();
}
};

int main()
{
MyType<uint32_t> m;
std::cout << m.Field;
}


I get a compiler warning because it looks like the the condition sizeof(MyType) == 4 is not being evaluated at compile time.

If it were we would have specialization and this wouldn't be a problem.

Anyway I could achieve this?

EDIT: What I really want to achieve is this:

Field = ReadLong();


OR

Field = ReadInt();


through template meta-programming. Can I do this without specializing the Read* functions? Given how powerful c++ templates I really feel like I'm missing some aha moment, because if I have to specialize Read* functions the rabbit hole continues to go deeper.

Answer
warning C4244: '=': conversion from 'int64_t' to 'uint32_t', possible loss of data for the int specialization 

I get a compiler warning because it looks like the the condition sizeof(MyType) == 4 is not being evaluated at compile time.

No, it doesn't look like that at all. Whether the comparison is evaluated at compile time or not has no effect on the return type of the conditional expression.

If we simplify the example by removing the conditional and expand the template using the type that you used, we get this minimal reproduction:

uint32_t Field;
int64_t temp = ReadInt(); // temporary variable for exposition
Field = temp;

Clearly, the int64_t may contain a value that is much greater than can be represented by int32_t. If it does, then the correct value will be lost in conversion. The compiler warns about this possibility.


Why is the type of the conditional int64_t specifically you might ask. Well, the types of each subexpression is int64_t and uint32_t respectively, so the type must be one of those. The type of Field or the result of the comparison - even if evaluated at compile time - has no effect on which type is chosen. Instead, integral promotion rules are used in this case.


Anyway I could achieve this?

A template function should work:

template<class T>
T Read() {
    static_assert(false, "not implemented");
}

template<>
uint64_t Read<uint64_t>() {
    return ReadLong();
}

template<>
uint32_t Read<uint32_t>() {
    return ReadInt();
}

// ...
Field = Read<SizeType>();