Martin Perry
Martin Perry

Reputation: 9527

C++ auto deduce type of template member pointer

I have this code:

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);
}

However, compilation ends with an error:

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

I am using Visual Studio 2019 with C++17 enabled.

Why is construction cannot be auto-deduced? Or is it even possible?

Upvotes: 4

Views: 843

Answers (3)

D. Sikilai
D. Sikilai

Reputation: 497

It seems c++ forgot to include auto deduction for member pointers in TMP. I have tried with c++20 and failed. This is a big issue. But we can have a workaround for the same as in the following.

  • Consindering your code should work but doesn't due to 'c++ limitations'. We will only modify a bit of it. Do as follows.
    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);
    }
    

    The above coding finally works.

    Upvotes: 2

  • Davis Herring
    Davis Herring

    Reputation: 39818

    Clang is correct that this code is valid: auto can be used as a decl-specifier for any kind of declaration of a function, variable, or template parameter. You can’t, on the other hand, use it in other places in the declaration:

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

    Upvotes: 2

    Micha&#235;l Roy
    Micha&#235;l Roy

    Reputation: 6471

    Your template needs more info than that to do what you want. It is also very difficult, if not impossible to do what you want without using 'questionable' (one should avoid using reinterptret_cast<>) pointer gymnatics. And using macros will give us some syntactic sugar.

    This example uses references for ease of use, modifying to use (or add suuport for) pointers should be easy enough.

    #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{});
    }
    

    Upvotes: -3

    Related Questions