Search code examples
c++template-argument-deductionfunction-templates

C++ funtion template deduction of type from member type?


I'm trying to write a C++ function template that, when called with an argument of a classes member alias, the function can deduce the class of that alias. A simplified version of what would be nice:

class A {
  public :
    using x_t = float;
    void whatami() {printf("A\n");}
};

class B {
  public :
    using x_t = int;
    void whatami() {printf("B\n");}
};

template<typename T>
void fn(T::x_t x) {
  T t;
  t.whatami();
};

...
A::x_t a;
fn(a);
...

The g++ 7.3 error is:

play$ g++ --std=c++11 x.cpp
x.cpp:19:12: error: variable or field ‘fn’ declared void
 void fn(T::x_t x) {
            ^~~
x.cpp:19:16: error: expected ‘)’ before ‘x’
 void fn(T::x_t x) {
                ^
x.cpp: In function ‘int main(int, char**)’:
x.cpp:29:3: error: ‘fn’ was not declared in this scope
   fn(a);
   ^~

I'm interpreting this to mean that C++ can't deduce T as in fn(T::x_t x) but I'm not sure.

Thoughts?


Solution

  • It can't deduce T from float, which is what a is. It wont treat typedefs with any special consideration when trying to fill your template. It's also not going to search the whole type space for any type with a member by that name.

    One way to get that deduction is to boost that member type with a reference to the owning class, and add an implicit conversion back to the primitive:

    template <typename TOwner, typename TData>
    class x_t {
        TData data;
    public:
        constexpr x_t() = default;
        constexpr x_t(TData data) : data(data) { }
        constexpr operator TData() const { return data; }
    };
    
    struct A {
        using x_t = x_t<A, float>;
        /*...*/
    };
    

    Then partial specialization will work correctly:

    template<typename TOwner, typename TData>
    void fn(x_t<TOwner, TData> x) {
      TOwner t;
      t.whatami();
    };
    

    And you can use an instance in any type-safe function

    A::x_t a(42.0);
    std::cout << a; // 42.0
    

    (sadly, printf doesn't count as type-safe. using streams here, fmt::printf from the excellent fmt library would also work).

    It probably doesn't reach your goal of making it easy to deduce from a member, but it's a possibility.