就默认模板参数、SFINAE 或 std::enable_if
的重载分辨和模板参数替换而言,这两个版本之间没有任何区别。我也看不到为什么会有模块的不同,因为它们并不改变编译器需要看到成员函数的全部定义这一事实。
可读性
内外联版本的一个主要优点是可读性。您只需声明和记录成员函数,甚至可以将定义移动到在最后包含的单独文件中。这样,类模板的读者就不必跳过可能大量的实现细节,可以只阅读摘要。
对于您的特定示例,您可以有以下定义。
template<typename T>
template<typename... Args>
void MyType<T>::test(Args... args) const {
// do things
}
在名为
MyType_impl.h
的文件中实现,然后让
MyType.h
文件仅包含声明。
template<typename T>
struct MyType {
template<typename... Args>
void test(Args...) const;
};
#include "MyType_impl.h"
如果
MyType.h
包含足够的关于
MyType
函数的文档,则大部分时间使用该类的用户不需要查看
MyType_impl.h
中的定义。
表达能力
但是,除了增加可读性之外,内联和外部定义还有所不同。虽然每个内联定义都可以轻松地移动到外部定义中,但反之则不然。也就是说,外部定义比内联定义更具表现力。当您有紧密耦合的类相互依赖于彼此的功能,以至于前向声明不足时,就会发生这种情况。
例如,如果要支持命令模式的命令链并且使其支持用户定义的函数和函数对象而无需它们继承自某个基类,则出现此类情况。因此,这种
Command
本质上是
std::function
的 "改进" 版本。
这意味着
Command
类需要某种形式的类型擦除,我将在此处省略,但如果有人真的想要我包括它,我可以添加它。
template <typename T, typename R>
class Command ;
那么如何实现
.then
呢?最简单的方法是创建一个帮助类,它存储原始的
Command
和需要在其后执行的
Command
,然后按顺序调用它们的调用运算符:
template <typename T, typename R, typename U>
class CommandThenHelper {
public:
CommandThenHelper(Command<T,R>, Command<R,U>);
U operator() (T const& val) {
return _snd(_fst(val));
}
private:
Command<T, R> _fst;
Command<R, U> _snd;
};
请���意,在此定义点上,Command 不能是不完整的类型,因为编译器需要知道 Command 和 Command 实现了调用运算符以及它们的大小,所以这里不足的前向声明是不够的。即使您通过指针存储成员命令,在定义 operator() 时也绝对需要 Command 的完整声明。
有了这个帮助器,我们可以实现 Command::then:
template <typename T, R>
template <typename U>
Command<T, U> Command<T,R>::then(Command<R, U> next) {
return CommandNextHelper<T, R, U>(*this, next);
}
请注意,如果仅对
CommandNextHelper
进行前向声明,则此方法无法使用,因为编译器需要知道
CommandNextHelper
的构造函数的声明。由于我们已经知道
Command
类声明必须在
CommandNextHelper
之前声明,这意味着您不能在类内定义
.then
函数,其定义必须在
CommandNextHelper
声明之后。
我知道这不是一个简单的例子,但我想不到更简单的例子,因为当您绝对必须将某些运算符定义为类成员时,这个问题最常见。这主要适用于表达式模板中的operator()
和operator[]
,因为这些运算符不能定义为非成员。
结论
总之:这主要取决于你的口味,因为两者之间没有太大的区别。只有当类之间存在循环依赖关系时,您才不能使用类内定义来定义所有成员函数。我个人更喜欢使用外部定义,因为将函数声明外包的技巧还可以帮助文档生成工具(例如doxygen),然后只为实际类创建文档,而不是为在另一个文件中定义和声明的其他辅助程序创建文档。
编辑
如果我正确理解您对原始问题的编辑,您想看看通用SFINAE、std::enable_if
和默认模板参数在这两种变体中的实现方式。声明看起来完全相同,只有在定义中您必须删除任何默认参数。
Default template parameters
template <typename T = int>
class A {
template <typename U = void*>
void someFunction(U val) {
}
};
vs
template <typename T = int>
class A {
template <typename U = void*>
void someFunction(U val);
};
template <typename T>
template <typename U>
void A<T>::someFunction(U val) {
}
enable_if
in default template parameter
template <typename T>
class A {
template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>
bool someFunction(U const& val) {
}
};
vs
template <typename T>
class A {
template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>>
bool someFunction(U const& val);
};
template <typename T>
template <typename U, typename>
bool A<T>::someFunction(U const& val) {
}
enable_if
as non-type template parameter
template <typename T>
class A {
template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
bool someFunction(U const& val) {
}
};
vs
template <typename T>
class A {
template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
bool someFunction(U const& val);
};
template <typename T>
template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int>>
bool A<T>::someFunction(U const& val) {
}
Again, it is just missing the default parameter 0.
SFINAE in return type
template <typename T>
class A {
template <typename U>
decltype(foo(std::declval<U>())) someFunction(U val) {
}
template <typename U>
decltype(bar(std::declval<U>())) someFunction(U val) {
}
};
vs
template <typename T>
class A {
template <typename U>
decltype(foo(std::declval<U>())) someFunction(U val);
template <typename U>
decltype(bar(std::declval<U>())) someFunction(U val);
};
template <typename T>
template <typename U>
decltype(foo(std::declval<U>())) A<T>::someFunction(U val) {
}
template <typename T>
template <typename U>
decltype(bar(std::declval<U>())) A<T>::someFunction(U val) {
}
This time, since there are no default parameters, both declaration and definition actually look the same.
inline
在这里没有作用。 - Quentin