关于c ++:SFINAE用于检测非成员模板功能的存在

SFINAE for detecting existence of non-member template function

tl;dr我想编写一个模板函数Process(T value),根据非成员函数CreateProcessor()的存在,它对不同的值的行为不同。我该怎么办?

我对斯芬娜有意见。假设我们需要支持函数CreateProcessor,它为某些类型的T返回接口IProcessor的实现。

在C++中,我们不能创建仅在返回类型中不同的函数的多个重载,所以我们必须使函数CreateProcessor也成为EDCOX1(4)所表示的模板函数。

现在假设我们要编写一个模板函数Process(T value),它根据CreateProcessor()的存在而不同,即在实现CreateProcessor()时,它应该使用处理器处理value,否则会导致错误。

我试图写以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <cstdio>
#include <type_traits>

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t.
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

// An interface for a processor that receives a value of specific type.
template<class T>
class IProcessor {
public:
    virtual void process(T value) = 0;
};

// A processor for int.
class IntProcessor : public IProcessor<int> {
public:
    virtual void process(int value) override {
        printf("IntProcessor::process is called for value = %d
"
, value);
    }
};

// Template prototype.
template<class T>
IProcessor<T>* CreateProcessor();

// Template specialization for int.
template<>
IProcessor<int>* CreateProcessor() {
    return new IntProcessor();
}

// Detector of CreateProcessor.
template<class, class=void>
struct CreateProcessorImplemented : std::false_type { };

template<class T>
struct CreateProcessorImplemented<T, void_t<decltype(CreateProcessor<T>())>> : std::true_type { };


// Specializations depending on existence of CreateProcessor.
template <typename T>
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    IProcessor<T>* processor = CreateProcessor<T>();
    processor->process(value);
}

template <typename T>
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    printf("Processor for requested typename is unavailable
"
);
}


int main() {
    Process(42);
    Process("abc");

// static_assert(!CreateProcessorImplemented<char const*>::value,":(");
/* This static_assert fails with an error:
 * code.cpp:56:5: error: static assertion failed: :(
 *      static_assert(!CreateProcessorImplemented<char const*>::value,":(");
 */

}

尽管这会导致链接错误:

1
2
/tmp/ccTQRc9N.o:code.cpp:function std::enable_if<CreateProcessorImplemented<char const*, void>::value, void>::type Process<char const*>(char const*): error: undefined reference to 'IProcessor<char const*>* CreateProcessor<char const*>()'
collect2: error: ld returned 1 exit status

我的想法是,当我们解决CreateProcessorImplemented时,decltype(CreateProcessor())不会失败,因为有一个模板原型IProcessor CreateProcessor(),编译器认为decltype等于IProcessor,这是合乎逻辑的,但不是我需要的。


使其工作的一种方法是使用包装结构来这样运行CreateProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <cstdio>
#include <type_traits>

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t.
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

// An interface for a processor that receives a value of specific type.
template<class T>
class IProcessor {
public:
    virtual void process(T value) = 0;
};

// A processor for int.
class IntProcessor : public IProcessor<int> {
public:
    virtual void process(int value) override {
        printf("IntProcessor::process is called for value = %d
"
, value);
    }
};

// Template prototype.
template<class T>
struct ProcessorCreator: std::false_type {
   static IProcessor<T>* CreateProcessor();
};

// Template specialization for int.
template<>
struct ProcessorCreator<int>: std::true_type {
static IProcessor<int>* CreateProcessor() {
    return new IntProcessor();
}
};

// Detector of CreateProcessor.
template<class, class=void>
struct CreateProcessorImplemented : std::false_type { };

template<class T>
struct CreateProcessorImplemented<T, typename std::enable_if<ProcessorCreator<T>::value>::type > : std::true_type { };


// Specializations depending on existence of CreateProcessor.
template <typename T>
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    IProcessor<T>* processor = ProcessorCreator<T>::CreateProcessor();
    processor->process(value);
}

template <typename T>
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    printf("Processor for requested typename is unavailable
"
);
}


int main() {
    Process(42);
    Process("abc");

// static_assert(!CreateProcessorImplemented<char const*>::value,":(");
/* This static_assert fails with an error:
 * code.cpp:56:5: error: static assertion failed: :(
 *      static_assert(!CreateProcessorImplemented<char const*>::value,":(");
 */

}

或者,可以删除模板声明,并使用函数重载传递IProcessor模板参数类型--方法是创建伪参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <cstdio>
#include <type_traits>

// A workaround for void_t as described here: http://en.cppreference.com/w/cpp/types/void_t.
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

// An interface for a processor that receives a value of specific type.
template<class T>
class IProcessor {
public:
    virtual void process(T value) = 0;
};

// A processor for int.
class IntProcessor : public IProcessor<int> {
public:
    virtual void process(int value) override {
        printf("IntProcessor::process is called for value = %d
"
, value);
    }
};


IProcessor<int>* CreateProcessor(const int&) {
    return new IntProcessor();
}

// Detector of CreateProcessor.
template<class, class=void>
struct CreateProcessorImplemented : std::false_type { };

template<class T>
struct CreateProcessorImplemented<T, void_t<decltype(CreateProcessor(std::declval<T>()))>> : std::true_type { };


// Specializations depending on existence of CreateProcessor.
template <typename T>
typename std::enable_if<CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    IProcessor<T>* processor = CreateProcessor(value);
    processor->process(value);
}

template <typename T>
typename std::enable_if<!CreateProcessorImplemented<T>::value, void>::type Process(T value) {
    printf("Processor for requested typename is unavailable
"
);
}


int main() {
    Process(42);
    Process("abc");

// static_assert(!CreateProcessorImplemented<char const*>::value,":(");
/* This static_assert fails with an error:
 * code.cpp:56:5: error: static assertion failed: :(
 *      static_assert(!CreateProcessorImplemented<char const*>::value,":(");
 */

}