C++ 中方法参数类型的静态自省
Static introspection of method argument type in C++
我想制作一个模板化的 class,它包含另一个 class 的实例,并使用正确的参数类型转发其中一个 foo
方法。是否有一种聪明的元编程方式来执行内部方法的 "perfect forwarding" ?
template <typename Inner>
class Outer {
private:
Inner inner;
public:
// To-do: replicate foo method of Inner with identical signature,
// how to pick correct T?
void foo(T arg) { inner.foo(arg); }
};
我可以看到两个 classic 解决方案,但是有更好的现代元编程解决方案吗?
- 具有部分保护接口的继承:
Outer
可以 public 仅继承自 Inner
。但是 Inner 也有只能由 Outer 而不是用户调用的方法。这些可以是 protected
,好的,但它也紧密耦合了 Outer
和所有类型的 Inner
classes 的实现。 Outer
的public接口可以通过Inner
中的public方法任意扩展,这是不可取的。
- 使 foo 成为模板函数
template <typename T> void foo(T&& arg) { inner.foo(std::forward<T>(arg)); }
。这是参数的完美转发,但是如果用户使用错误的参数调用 foo
,则错误报告 Inner::foo
而不是 Outer::foo
。这破坏了 Outer
. 的 public 接口提供的封装
也许是这样的:
template <typename Inner>
class Outer : private Inner {
public:
using Inner::foo;
};
这是给出几乎完美错误消息的一种方法:
#include <string>
// a type which yields the type we gave it
template<class T> struct passer
{
using type = T;
};
// an easy-to-use alias
template<class T> using pass_t = typename passer<T>::type;
// example
template <typename Inner>
class Outer {
private:
Inner inner;
public:
// To-do: replicate foo method of Inner with identical signature,
// how to pick correct T?
// Ans: with a pass_t
template<class T>
auto foo(T&& arg)
-> pass_t<decltype(this->inner.foo(std::forward<T>(arg)))>
{
return inner.foo(std::forward<T>(arg));
}
};
struct Bar
{
void foo(std::string const& thing);
};
struct Baz
{
int foo(int thing) { return thing * 2; };
};
int main()
{
auto o = Outer<Bar>();
o.foo(std::string("hi"));
o.foo("hi");
int i = 1;
/* - uncomment for error
o.foo(i);
note the nice error message on gcc:
<source>:41:7: error: no matching member function for call to 'foo'
<source>:19:10: note: candidate template ignored: substitution failure [with T = int]: reference to type 'const std::string' (aka 'const basic_string<char>') could not bind to an lvalue of type 'int'
*/
// same here:
// o.foo(1);
// but this is fine
auto o2 = Outer<Baz>();
auto x = o2.foo(2);
// and this is not
// note: candidate template ignored: substitution failure [with T = char const (&)[6]]: cannot initialize a parameter of type 'int' with an lvalue of type 'char const[6]'
// auto y = o2.foo("dfghj");
}
Link 这里:https://godbolt.org/g/UvsrbP
我想制作一个模板化的 class,它包含另一个 class 的实例,并使用正确的参数类型转发其中一个 foo
方法。是否有一种聪明的元编程方式来执行内部方法的 "perfect forwarding" ?
template <typename Inner>
class Outer {
private:
Inner inner;
public:
// To-do: replicate foo method of Inner with identical signature,
// how to pick correct T?
void foo(T arg) { inner.foo(arg); }
};
我可以看到两个 classic 解决方案,但是有更好的现代元编程解决方案吗?
- 具有部分保护接口的继承:
Outer
可以 public 仅继承自Inner
。但是 Inner 也有只能由 Outer 而不是用户调用的方法。这些可以是protected
,好的,但它也紧密耦合了Outer
和所有类型的Inner
classes 的实现。Outer
的public接口可以通过Inner
中的public方法任意扩展,这是不可取的。 - 使 foo 成为模板函数
template <typename T> void foo(T&& arg) { inner.foo(std::forward<T>(arg)); }
。这是参数的完美转发,但是如果用户使用错误的参数调用foo
,则错误报告Inner::foo
而不是Outer::foo
。这破坏了Outer
. 的 public 接口提供的封装
也许是这样的:
template <typename Inner>
class Outer : private Inner {
public:
using Inner::foo;
};
这是给出几乎完美错误消息的一种方法:
#include <string>
// a type which yields the type we gave it
template<class T> struct passer
{
using type = T;
};
// an easy-to-use alias
template<class T> using pass_t = typename passer<T>::type;
// example
template <typename Inner>
class Outer {
private:
Inner inner;
public:
// To-do: replicate foo method of Inner with identical signature,
// how to pick correct T?
// Ans: with a pass_t
template<class T>
auto foo(T&& arg)
-> pass_t<decltype(this->inner.foo(std::forward<T>(arg)))>
{
return inner.foo(std::forward<T>(arg));
}
};
struct Bar
{
void foo(std::string const& thing);
};
struct Baz
{
int foo(int thing) { return thing * 2; };
};
int main()
{
auto o = Outer<Bar>();
o.foo(std::string("hi"));
o.foo("hi");
int i = 1;
/* - uncomment for error
o.foo(i);
note the nice error message on gcc:
<source>:41:7: error: no matching member function for call to 'foo'
<source>:19:10: note: candidate template ignored: substitution failure [with T = int]: reference to type 'const std::string' (aka 'const basic_string<char>') could not bind to an lvalue of type 'int'
*/
// same here:
// o.foo(1);
// but this is fine
auto o2 = Outer<Baz>();
auto x = o2.foo(2);
// and this is not
// note: candidate template ignored: substitution failure [with T = char const (&)[6]]: cannot initialize a parameter of type 'int' with an lvalue of type 'char const[6]'
// auto y = o2.foo("dfghj");
}
Link 这里:https://godbolt.org/g/UvsrbP