2

A beginner's question here, so I have the following code

foo.h

enum class Fruit : uint16_t {
    Apple  = 8019U,
    Orange = 8020U,
    Banana = 8021U,
    Cactus = 8022U
};

class Foo {
  public:
    template<typename T> void SetValue(unsigned int location, const T& value);
    template<typename T> void SetValue(unsigned int location, const T* value_ptr);
    template<typename T> void SetValue(Fruit fruit, const T& value);
    template<typename T> void SetValue(Fruit fruit, const T* value_ptr);
}

foo.cpp

template<typename T>
void Foo::SetValue(unsigned int location, const T& value) {
    std::cout << value << std::endl;
}

template<typename T>
void Foo::SetValue(unsigned int location, const T* value_ptr) {
    std::cout << (*value_ptr) << std::endl;
}

template<typename T>
void Foo::SetValue(Fruit fruit, const T& value) {
    SetValue<T>(static_cast<unsigned int>(fruit), value);
}

template<typename T>
void Foo::SetValue(Fruit fruit, const T* value_ptr) {
    SetValue<T>(static_cast<unsigned int>(fruit), value_ptr);
}

#define INSTANTIATE_TEMPLATE(T) \
    template void Foo::SetValue<T>(unsigned int location, const T& value); \
    template void Foo::SetValue<T>(unsigned int location, const T* value_ptr); \
    template void Foo::SetValue<T>(Fruit fruit, const T& value); \
    template void Foo::SetValue<T>(Fruit fruit, const T* value_ptr);

INSTANTIATE_TEMPLATE(int)
INSTANTIATE_TEMPLATE(uint)
INSTANTIATE_TEMPLATE(bool)
INSTANTIATE_TEMPLATE(float)
INSTANTIATE_TEMPLATE(vec2)
INSTANTIATE_TEMPLATE(vec3)
INSTANTIATE_TEMPLATE(vec4)
INSTANTIATE_TEMPLATE(mat2)
INSTANTIATE_TEMPLATE(mat3)
INSTANTIATE_TEMPLATE(mat4)
...

#undef INSTANTIATE_TEMPLATE

main.cpp

int main() {
    Foo foo;

    float bar[7] { 0.0f, 0.15f, 0.3f, 0.45f, 0.6f, 0.75f, 0.9f };
    float baz = 12.5f;

    foo.SetValue(Fruit::Orange, 1.05);  // calling SetValue<double>(Fruit fruit, const double& value)

    foo.SetValue(Fruit::Apple, bar + 3);  // calling SetValue<float*>(Fruit fruit, float *const& value)
    foo.SetValue(Fruit::Cactus, &baz);    // calling SetValue<float*>(Fruit fruit, float *const& value)
}

In the last 2 calls, while I really intended to call
SetValue<float>(Fruit fruit, const float* value_ptr)

What actually gets called are
SetValue<float*>(Fruit fruit, float *const& value)

I'm aware that this is ambiguous because VS Intellisense was not able to color highlight SetValue<T> in the overridden function body, so I tried to write foo.SetValue<float>(Fruit::Cactus, &baz) instead, now the compiler interprets T as float rather than float*, but I wonder if this is a horrible design...... Is there a better approach that doesn't require me to explicitly specify the type of T? How can I eliminate the ambiguity completely?

neo-mashiro
  • 422
  • 4
  • 13
  • You could play with SFINAE, but... just use different names for functions taking values and pointers. – Evg Jan 13 '22 at 09:46
  • As to "horrible design": do you really need to have both overloads, for pointers and for references? Would it be so bad to have an extra `*` or `&` at some call sites? – Thomas Jan 13 '22 at 09:48
  • 1
    [OT]: See [why-can-templates-only-be-implemented-in-the-header-file](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). – Jarod42 Jan 13 '22 at 09:50
  • @Thomas Hmm, I think you are right, maybe I just need the `const T&` version, but if `T` is a pointer type, my function wants to bind to it and observe changes automatically, is there a way I can tell if `T` is a pointer? – neo-mashiro Jan 13 '22 at 10:02
  • 1
    Difference in behaviour depending on whether you pass a pointer or a reference? That's _definitely_ smelly design in my opinion. Maybe name the pointer-accepting version `BindValue` or `WatchValue` or something like that, instead of `SetValue`. – Thomas Jan 13 '22 at 10:09
  • @Thomas, Yes I agree, I should get rid of the pointer version by including another parameter `bool bind`, much clearer to read. – neo-mashiro Jan 13 '22 at 10:25
  • If we're having a design discussion anyway: I stand by my recommendation of having a _differently named_ function. How does a reader know what the `true` means in `SetValue(Fruit::Cactus, &baz, true)`? Whereas with `BindValue(Fruit::Cactus, &baz)` it's much clearer. – Thomas Jan 13 '22 at 10:33
  • @Thomas, you're right, speaking of readability I'll surely take your advice. – neo-mashiro Jan 13 '22 at 10:39

2 Answers2

2

I'm aware that this is ambiguous

It is not.

const T& value is a better match than const T* value for float*. The former is an exact match, whereas the later require a qualification conversion.

You might drop the const for pointer:

template<typename T> void SetValue(Fruit fruit, T* value_ptr);

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

If you do want do use SFINAE.

 template<typename T, std::enable_if_t<!std::is_pointer<T>::value, bool> = false> 
// no difference between = true or = false
 void SetValue(unsigned int location, const T& value) {
         std::cout << value << std::endl;
 }

 template<typename T>
 void SetValue(unsigned int location, const T* value_ptr) {
         std::cout << (*value_ptr) << std::endl;
 }

Just use this to replace the above 2 functions(the ones which cout). I don't think the 4th function(Fruit fruit, const T* value_ptr) is necessary.

Robin Dillen
  • 704
  • 5
  • 11
  • Very nice solution~ but SFINAE seems really ugly to me... – neo-mashiro Jan 13 '22 at 10:43
  • @neo-mashiro SFINAE itself is not ugly, but the interface that could easily bewilder a code reader is. These two `setValue` functions do different things and have different semantics. They should not have the same name. – Evg Jan 13 '22 at 11:04
  • 1
    SFINAE might be replaced by concept/constraint (C++20), which has better syntax: `template requires(!std::is_pointer::value) void SetValue(unsigned int location, const T& value)`. – Jarod42 Jan 13 '22 at 12:25
  • *"I don't really know if there is a difference between `= true` or `= false`"*. There is none in that context. – Jarod42 Jan 13 '22 at 12:25
  • @Jarod42, I guessed that but wasn't sure! – Robin Dillen Jan 13 '22 at 19:25