Search code examples
c++templatesc++17compile-time

How to define conversion to class template working when used as function argument


Having this template:

template <bool X> 
struct Foo {
  Foo(int v) : v(v) {}
  int v;
};

I can say that by default Foo should be of False with deduction guide:

Foo(int)->Foo<false>;

Thank's to which this code works:

Foo a = 5;

My problem is, how can i make this work when Foo is used as function argument:

template <bool X> 
void f(Foo<X> foo) { 
  cout << "Foo<" << X << ">(" << foo.v << ")" << endl; 
}
f(5); // error: no matching function for call to 'f'
      // candidate template ignored: could not match 'Foo<X>' against 'int'

I tried saying somehow f that X is by default false but f (I mean compiler) is not listening to me:

template <bool X = false> // = false changes nothing, same error 
void f(Foo<X> foo) {
  cout << "Foo<" << X << ">(" << foo.v << ")" << endl; 
}
template <bool X> 
struct get_bool { // to force looking at f::X 
  static constexpr bool value = X; 
};

template <bool X = false> 
void f(Foo<get_bool<X>::value> foo) { 
  cout << "Foo<" << X << ">(" << foo.v << ")" << endl;         
  /* this is not working because 
   * get_bool evaulates before 
   * args matching and in the end, 
   * this function could be defined 
   * as: void f(Foo<false>) 
   */
}

I don't mind introducing some additional helper classes etc. I was hoping that maybe some decltype, auto, some_trait<> or additional helper class (/es) magic could help here to solve this problem which I think I could also sum up as: How to define a deduction guide for function?


Solution

  • The problem with template <bool X = false> void f(Foo<X> foo) is that implicit conversions are not allowed when passing arguments to parameters that are used in template parameter deduction.

    You can either add an extra overload of f accepting an int, or not make f a template at all:

    struct Bar
    {
        bool x = false;
        int v = 0;
    
        template <bool X>
        Bar(Foo<X> foo) : x(X), v(foo) {}
    
        Bar(int v) : v(v) {}
    };
    
    void f(Bar bar) {...}
    

    It means the boolean is no longer constexpr inside of f. If you want it to be constexpr, there's a trick you can use:

    void f(Bar bar)
    {
        auto lambda = [&](auto x_value)
        {
            constexpr bool x = x_value;
            // Here `x` is `constexpr`.
        };
    
        if (bar.x)
            lambda(std::true_type{});
        else
            lambda(std::false_type{});
    }