Function Overloading Based on Arbitrary Properties

2019-06-08 03:13发布

问题:

In the example below, I need to extract some values. I have an efficient extractor, which can work with builtin types, and an inefficient template that can work with everything. To choose between these, I want to use Function Overloading Based on Arbitrary Properties of Types. Here is my code:

#include <string>
#include <iostream>

class extractor
{
public:
    static void extract(const bool& val) { std::cout << "Specialized extractor called" << std::endl; }
    static void extract(const double& val) { std::cout << "Specialized extractor called" << std::endl; }
};

template <typename T>
void extract_generic(const T& value) { std::cout << "Generic extractor called" << std::endl; }


template <typename T> struct is_extractor_native { static const bool val = false; };
template<> struct is_extractor_native<bool> {static const bool val = true; };
template<> struct is_extractor_native<double>  {static const bool val = true; };


template <bool B, class T = void>
struct enable_if {
    typedef T type;
};

template <class T>
struct enable_if<false, T> {};

template <typename T>
struct extract_caller
{

    //line 32 below
    static void extract(const T& val, typename enable_if<is_extractor_native<T>::val>::type * = 0)
    {
            extractor::extract(val);
    }

    //line 37 below
    static void extract(const T& val, typename enable_if<!is_extractor_native<T>::val>::type * = 0)
    {
            extract_generic(val);
    }
};



int main(void)
{
    std::string string_value("hello");
    double double_value(.123);

    std::cout << "native extractor for std::string: " << (int)is_extractor_native<std::string>::val << std::endl;
    std::cout << "native extractor for double:      " << (int)is_extractor_native<double>::val << std::endl;

    extract_caller<std::string>::extract(string_value);
    extract_caller<double>::extract(double_value);

    return 0;
}    

When I build the compiler complains:

g++     main.cpp   -o main
main.cpp: In instantiation of ‘extract_caller<std::basic_string<char> >’:
main.cpp:50:29:   instantiated from here
main.cpp:32:14: error: no type named ‘type’ in ‘struct enable_if<false, void>’
main.cpp: In instantiation of ‘extract_caller<double>’:
main.cpp:51:24:   instantiated from here
main.cpp:37:14: error: no type named ‘type’ in ‘struct enable_if<false, void>’
make: *** [main] Error 1

When comment out the extraction and have only the traits printed, I get correct results:

./main
native extractor for std::string: 0
native extractor for double:      1

On the error listing you can see that for double the compiler passes the prototype on line 32, goes to 37 and prints error. The question is, why the SFINAE principle is not applied here?

回答1:

SFINAE only works when certain errors (like the ones you have) happen in declaration part. In your case they happen in definition. You have to rewrite your code so that enable_if is used in declaration, in your case of struct extract_caller.



回答2:

The code I got by fixing the problem doublep mentioned:

struct extract_caller
{
    template <typename T>
    static void extract(const T& val, typename enable_if<(bool)is_extractor_native<T>::val>::type * = 0)
    {
            extractor::extract(val);
    }

    template <typename T>
    static void extract(const T& val, typename enable_if<!(bool)is_extractor_native<T>::val>::type * = 0)
    {
            extract_generic(val);
    }
};

This can be used as

extract_caller::extract(string_value);
extract_caller::extract(double_value);