1

I just wrote my functions for serialization or (some) objects and for some native types and encountered a curious error:

<source>: In function 'int main()':
<source>:29:34: error: call of overloaded 'deserialize<long long int>(const char [5])' is ambiguous
   29 |     deserialize<long long>("1337");
      |                                  ^
<source>:15:3: note: candidate: 'T deserialize(const string&) [with T = long long int; std::string = std::__cxx11::basic_string<char>]'
   15 | T deserialize(std::string const &);
      |   ^~~~~~~~~~~
<source>:26:11: note: candidate: 'typename std::enable_if<(((! is_specialization<T, std::vector>::value) && (! is_specialization<T, std::map>::value)) && (! is_specialization<T, std::tuple>::value)), T>::type deserialize(const string&) [with T = long long int; typename std::enable_if<(((! is_specialization<T, std::vector>::value) && (! is_specialization<T, std::map>::value)) && (! is_specialization<T, std::tuple>::value)), T>::type = long long int; std::string = std::__cxx11::basic_string<char>]'
   26 | long long deserialize<long long>(std::string const & llstr) { return {}; }
      |           ^~~~~~~~~~~~~~~~~~~~~~

I have a prototype, an overload and a specialization like this:

#include <string>
#include <type_traits>
#include <vector>
#include <map>
#include <tuple>

template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};

// Prototype
template <typename T>
T deserialize(std::string const &);

// Default Implementation (Overload)
template <typename T>
typename std::enable_if<!is_specialization<T, std::vector>::value &&
                        !is_specialization<T, std::map>::value &&
                        !is_specialization<T, std::tuple>::value, T>::type
deserialize(std::string const &) { return {}; }

// Specialization
template <>
long long deserialize<long long>(std::string const & llstr) { return {}; }

int main() {
    deserialize<long long>("1337");
}

If I call the specialization, like in the example from the error-message, I get the error above. But why is that? Shouldn't the specialization shadow the overload? And why does the compiler point to the prototype when showing the error?

I'd like to have a prototype in my hpp-file, but this code only works if I remove the prototype. How can I have both?

The implementation of is_specialization, 463035818-is-not-a-number. I got it from here.

As for me mentioning the prototype for the header, Remy Lebeau: I like to have two hpp and one cpp file: In the (main) hpp (for the class I want to implement, I put only the declarations, no definitions. In the "..._imp.hpp" (which is included at the end of the other hpp) I put definitions for function templates. The rest (non-template-definitions and full specifications of templated functions) I write in the cpp-file. For testing, I use an online ide, so it's all part of the same "file", but for the real project I want to split the code the way I just mentioned and I felt giving reason as to why I want a separate prototype was necessary.

Yes, for the deserialize-functions it makes most sense to me to differ by return type and as that's not possible I thought templates were the way to go. Thank you for the suggestion, Nathan Oliver. I'll look into it and see whether tag dispatch helps!

Dharman
  • 30,962
  • 25
  • 85
  • 135
ChaosNe0
  • 37
  • 7
  • 1
    what is `is_specialization` ? Please post a [mcve]. And please do not remove includes from the code – 463035818_is_not_an_ai Mar 23 '23 at 16:35
  • 3
    Function template specialization is often the wrong technique. Specializations do not participate in overload resolution, only the template does. They also don't hide or shadow anything. A specialization just tells the compiler "Hey, if you instantiate the template for this particular type, use this version instead of the primary version.". Often overloading is what you should do instead of specializing. – NathanOliver Mar 23 '23 at 16:36
  • @ChaosNe0 "*I'd like to have a prototype in my hpp-file*" - are you, by chance, trying to split your template prototypes and implementations between `.h` and `.cpp` files? If so, [that won't work](https://stackoverflow.com/questions/495021/). – Remy Lebeau Mar 23 '23 at 16:37
  • @NathanOliver generally yes, but here the template argument is not a function argument – 463035818_is_not_an_ai Mar 23 '23 at 16:38
  • 1
    cannot reproduce https://godbolt.org/z/8vqzMboqf (assuming `is_specialization` is not relevant, which it isnt, unless it is totally broken) – 463035818_is_not_an_ai Mar 23 '23 at 16:39
  • 1
    @463035818 Yeah, they basically want to overload based solely on the return type, which is not allowed in overloading. Not sure if specialization is the way to go here or instead switch to using tag dispatch instead. – NathanOliver Mar 23 '23 at 16:43

1 Answers1

0

Specialization can apply to both overloads. The compiler uses the last one declared. Thus the other overload remains without specialization.

This can be seen well if you swap the order of specialization with the second overload.

The simplest solution to this problem is to use if constexpr.

#include <string>
#include <type_traits>
#include <vector>
#include <map>
#include <tuple>

template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};

template <typename T>
T deserialize(std::string const &) {
    if constexpr (
        !is_specialization<T, std::vector>::value &&
        !is_specialization<T, std::map>::value &&
        !is_specialization<T, std::tuple>::value
    ) {
      return {};
    } else if constexpr(std::is_same_v<T, long long>){
      return {};
    } else {
      return {};
    }
}

int main() {
    deserialize<long long>("1337");
}
Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
  • the both return `T`, no? `long long` is no vector, no map and no tuple, so the `std::enable_if_t` is just `T` – 463035818_is_not_an_ai Mar 23 '23 at 16:57
  • I adjusted the answer, thanks for pointing out! – Benjamin Buch Mar 23 '23 at 17:17
  • Curious, I'd think the linker would make any call to the prototype go to the default-implementation. Is there a way to keep the prototype, though? Edit: Nevermind, you just edited the answer. I gotta check whether this works for me, but it already looks good! Thank you very much. :) – ChaosNe0 Mar 23 '23 at 17:18