Overloaded functions ambiguous using the SFINAE principle

I came across some code written in VS7.1 and now I'm trying to get it to work on MacOSX. The code snippet below, which I understand about SFINAE . From what I understand, the code is used at compile time, you know what type it is, relying on some magic to instantiate. In short, the correct overload is chosen by considering the template argument.

Here is the code I have. It is somewhat simplified to show only the problem.

template <typename T> struct SomeClass { }; template <> struct SomeClass<char> { typedef char Type; }; template <typename T> struct IsChar { typedef char Yes; typedef int No; template <typename U> static Yes Select(U*, typename SomeClass<U>::Type* p = 0); template <typename U> static No Select(U*, ...); static T* MakeT(); const static bool Value = sizeof(Select(MakeT())) == sizeof(Yes); }; 

I just use it like this:

 if (IsChar<int>::Value) { ... 

When compiling , the above code works well , and it selects the top-most class due to the lack of a typedef for Type when using int.

If now I use char ...

 if (IsChar<char>::Value) { ... 

... the compiler will complain about ambiguous selection functions because it does not know which one to use. From what I read, overload resolution gives the least preference to the ellipsis parameter (...). Therefore, he must know to choose the first.

The code worked fine, at least on VS7.1, but not on gcc for MacOSX, and not on gcc4.4 for Linux.

Any suggestions for fixing this? Maybe this is usually done differently?

Thanks!

UPDATE: I realized that the sample code I gave may be a little too simplified, because I believe that we do not check the type here, even if I mistakenly do so. I need to collect a little more information for you today, since I do not have the code here. Sorry for that.

UPDATE2: Even if my tradition is bad and this is because you are not familiar with the source code or using templates in this way. Meanwhile, I dig out a little more information, let it be assumed that these constructions are for some reason X and the names that I gave are all wrong, but what about the compiler problem? Why can't he choose the right overloaded function here? This is interesting to me too. As I said, I will return with a better explanation of what the common goal is.

Edit

After a closer look at the source code, boost :: integral_constant and boost :: enable_if are used, as suggested here. The problem is something specific, how the template arguments are output, and it does not work the way it was configured. However, following what George suggested at the end of my answer, I could fix things in order to accept things. I have the following:

 typedef char Yes; typedef int No; template <typename U> static Yes Select(typename SomeClass<U>::Type* p); template <typename U> static No Select(...); static const bool Value = sizeof(Select<T>(0)) == sizeof(Yes); 

It works well. During the experiments, I found that the presence of two functional parameters in the Select functions leads to a problem. I did not find a reason. I will come back to this when I better understand.

Thank you for your help. At least now I understand the principles here and how everything should work. Only some details that are still unknown.

+4
source share
2 answers

If I do not understand the intent, the above use case does not require the use of SFINAE. If you only want to statically assert the Type type, you can simply use something like this:

 template<class T1, class T2> struct SameType { static const bool Value = false; }; template<class T> struct SameType<T, T> { static const bool Value = true; }; template <typename T> struct IsChar { static const bool Value = SameType<T, char>::Value; }; 

If you really need to use this for SFINAE (i.e. disable / enable functions based on template parameters), just use the above in combination with something like Boosts enable_if :

 template<class T> typename boost::enable_if_c<IsChar<T>::Value, void>::type someFunction() { } 

Or, if you can go Boost all the way:

 template<class T> typename boost::enable_if<boost::mpl::is_same<T, char>, void>::type someFunction() { } 

Update

SomeClass this, if you really wanted to verify that SomeClass specialization has typedef Type , you can use this solution from here :

 template<class T> struct HasType { template<class U> static char (&test(typename U::Type const*))[1]; template<class U> static char (&test(...))[2]; static const bool Value = (sizeof(test< SomeClass<T> >(0)) == 1); }; 

In this case, IsChar is certainly wrong, although something like HasType or HasTypedefType would be more descriptive :)

+9
source

In my comment, I said that you usually do not use the results of these predicates as values ​​for if , and that is why:

 // assume we have has_typedef_type from the Wikipedia page: template <typename T> void foo(const T& x) { if (has_typedef_type<T>::value) { // the predicate is true, so T::type exists typename T::type y = x; } else { // the predicate is false, so T::type doesn't exist, do something else float y = x; } } 

This may look fine, but consider when the predicate is false, the compiler will try to compile this:

 // let say we called it with double void foo<double>(const double& x) { if (false) { // wait, double doesn't have this! typename double::type y = x; } else { float y = x; } } 

The problem is that the code, even if it is deleted with the dead code removed, is poorly formed. The solution is to make if compilation time, but first a little boiler stove:

 // in C++0x, these are defined in <type_traits>, but we'll do it ourselves // (Boost has these as well) typename <typename T, T Value> struct integral_constant { typedef T type; static const T value = Value; }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; 

With this in mind, we define our functions:

 namespace detail { // here are the real implementations template <typename T> void foo(const T& x, true_type) { // the predicate is true, so T::type exists typename T::type y = x; } template <typename T> void foo(const T& x, false_type) { // the predicate is false, so T::type doesn't exist, do something else float y = x; } } template <typename T> void foo(const T& x) { detail::foo(x, // chose which function to call, using the type of this: integral_constant<bool, has_typedef_type<T>::value>()); } 

Now everything is in order, because the branches are completely independent of each other.

+6
source

Source: https://habr.com/ru/post/1338064/


All Articles