C++ 自动推断模板成员指针的类型

C++ auto deduce type of template member pointer

我有这个代码:

struct Test {
    std::string s;
};

template <typename T,
         auto T::* groupPtr>    
         struct Base{
             using BaseType = typename std::decay<decltype(std::declval<T>().*groupPtr)>::type;

             void Process(const Test * e){
                 printf("%s\n", e->s.c_str());

                 BaseType tmp = e->*groupPtr;       
                 printf("%s\n", tmp.c_str());
             }
         };

int main(){
    Test t;
    t.s = "x";

    Base<Test, &Test::s> r;
    r.Process(&t);
}

然而,编译以错误结束:

main.cpp: error C2440: 'specialization': cannot convert from
'std::string Test::* ' to 'auto Test::* '

main.cpp: message : Types pointed to are unrelated; conversion
requires reinterpret_cast, C-style cast or function-style cast

main.cpp: error C3535: cannot deduce type for 'auto Test::* ' from
'int'

main.cpp: message : see reference to class template instantiation
'Base<Test,0>' being compiled

我正在使用启用了 C++17 的 Visual Studio 2019。

为什么构造不能自动推导?或者有可能吗?

您的模板需要的信息比您想要的更多。如果不使用 'questionable'(应避免使用 reinterptret_cast<>)指针体操,即使不是不可能,也非常困难。使用宏会给我们一些语法糖。

此示例使用引用以方便使用,修改为使用(或添加 suuport)指针应该很容易。

#include <cstddef>   // offsetof()
#include <iostream>  // std::cout
#include <string>

template <typename _ClassT, typename _MemberT, size_t _Offset>
struct Base {
  void Process(const _ClassT& e) {
    std::cout << GetMemberRef(e);  // your code used printf(), but you shouldn't
                                   // assume the type of the inner member.
                                   // That's the whole point of this exercise,
                                   // isn't it?
    auto& tmp = GetMemberRef(e);
    std::cout << tmp;
  }

  // the name of access functions is quite verbose, but that's for 
  // demonstration purposes only.

  static const _MemberT& GetMemberRef(const _ClassT& e) {
    return *reinterpret_cast<const _MemberT*>(
        reinterpret_cast<const char*>(&e) + _Offset);
  }

  static _MemberT& GetMemberRef(_ClassT& e) {
    return *reinterpret_cast<_MemberT*>(reinterpret_cast<char*>(&e) + _Offset);
  }
};

// utility to make instantiation a bit easier to read and write...
// I don't think there is a way to do that without a macro.
#define MakeBase(type, member) \
  Base<type, decltype(type::member), offsetof(type, member)> {}

// test the code...

struct Test {
  std::string s;
};

struct Test2 {
  int value = 42;
};

int main() {
  Test t;
  t.s = "x";

  // declaration is a bit awkward, but there are no ways around that.      
  Base<Test, decltype(Test::s), offsetof(Test, s)> r;
  r.Process(t);

  // using MakeBase utility macro is quite clean, though. 
  auto x = MakeBase(Test2, value);
  x.Process(Test2{});
}

C++ 似乎忘了在 TMP 中包含成员指针的自动推导。我试过使用 c++20 但失败了。这是一个大问题。但是我们可以有一个与以下相同的解决方法。

  • 考虑到您的代码应该可以工作,但由于 'c++ limitations' 而不能工作。我们只会修改它的一部分。如下操作。
    struct Test {
        std::string s;
    };
    template <typename T,typename BaseType,
            BaseType (T::*groupPtr)>
    struct Base{
        void Process(const Test * e){
            printf("%s\n", e->s.c_str());
    
            BaseType tmp = e->*groupPtr;
            printf("%s\n", tmp.c_str());
        }
    };
    
    int main(){
        Test t;
        t.s = "x";
        static constexpr auto (Test::*s)=&Test::s;
        Base<Test,std::decay<decltype(std::declval<Test>().*s)>::type, s> r;
        r.Process(&t);
    }
    

    上面的代码终于可以工作了。

  • Clang 认为此代码有效是正确的:auto 可用作 decl-specifier 用于任何类型的函数、变量或模板声明范围。另一方面,你不能在声明的其他地方使用它:

    int auto::*f() {…}      // not in a ptr-operator
    std::vector<auto> x=…;  // not in a template argument