Deduction Guide for a template template parameter

338 Views Asked by At

I have a set of structure's class as such:

template<typename T>
struct Foo {
    T x_;
    T y_;
    constexpr Foo(T x, T y) : x_{x}, y_{y} {}
};

template<typename T, typename U, template<U> class Func>
class Bar {
private:
    Foo<T> foo_;
    Func<U> func_
    size_t n_;
public:
    Bar(Foo<T> foo, size_t n, Func<U> func) :
      foo_{foo},
      n_{n},
      func_{func}
    {}
};

And I'm trying to create a deduction guide for this class template...

// Doesn't compile
template<typename T, typename U, template<U> class Func>
Bar(Foo<T>, U, Func<U>)->
Bar<T,U,Func>;

// Doesn't compile
template<typename T, typename U, template<U> class Func>
Bar(Foo<T>, U, Func)->
Bar<T,U,Func>;

I'm not sure of the proper syntax for this when the template argument happens to be a template itself where that templated argument will be a function pointer, function object, functor, or a lambda that the class will store.

When I try to use U within Func<> it states "type name is not allowed" and if I remove it to be just Func without any template arguments, it states, "argument list for template template parameter 'Func' is missing"...

My intended use of Bar looks like this:

template<typename T>
constexpr T funcA(T x) {
    return x;
}

template<typename T>
constexpr T funcB(T x) {
    return x*x;
}

int main() {
    Bar bar1{Foo{1.0, 3.0}, 1000, funcA<double>}; 
    Bar bar2{Foo{3.7, 4.0}, 500, funcB<float>};

    return 0;
}  


EDIT - This section is intended for user: piotr-skotnicki

Note: The above was a pseudo code with the same signatures as a representation of my classes... Now that I have access to my IDE again, here is the "real" source.

Integrator.h

#pragma once

//#include <type_traits>

template <typename Field>
struct Limits {
    Field lower;
    Field upper;

    constexpr Limits(Field a = 0, Field b = 0) : 
        lower{ a < b ? a : b }, 
        upper{ a < b ? b : a }
    {}
};    

template <typename LimitType, typename Func>
class Integrator {       
    //static_assert(std::is_invocable_v<Func&>, "Invalid callable");
private:
    Limits<LimitType> limits_;
    size_t step_size_;
    Func integrand_;

public:
    Integrator(Limits<LimitType> limits, size_t stepSize, Func integrand) :
        limits_{ limits },
        step_size_{ stepSize },
        integrand_{ integrand }
    {}

    constexpr auto evaluate() {
        auto distance = limits_.upper - limits_.lower;     
        auto dx = distance / step_size_;       
        return calculate(dx);
    }        

private:
    template<typename ValueType>
    constexpr auto calculate(ValueType dx) {
        ValueType result = 0.0;
        for (size_t i = 0; i < step_size_; ++i) {
            auto dy = integrand_(limits_.lower + i * dx);
            auto area = dy * dx;
            result += area;
        }
        return result;
    }

};

//template <typename LimitType, typename Func>
//Integrator(Limits<LimitType>, size_t, Func)
//->Integrator<LimitType, Func>;

main.cpp

#include <iostream>
#include <exception>

#include "Integrator.h"


double funcE(double x) {
    return x;
}

template <typename T>
constexpr T funcA_t(T x) {
    return x;
}    

// This Works! 
int main() {
    try {
        std::cout << "Integration of f(x) = x from a=3.0 to b=5.0\nwith an expected output of 8\n";
        Integrator integratorA{ Limits{3.0, 5.0}, 10000, funcA };
        std::cout << integratorA.evaluate() << '\n';    
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

// This was failing to compile... but now seems to work for some reason...
int main() {
    try {
        std::cout << "Integration of f(x) = x from a=3.0 to b=5.0\nwith an expected output of 8\n";
        Integrator integratorA{ Limits{3.0, 5.0}, 10000, funcA_t<double> };
        std::cout << integratorA.evaluate() << '\n';    
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

// Same as above...
Integrator integrator{ Limits{3.0, 5.0}, 10000, &funcA_t<double> };
// wasn't compiling...

Beforehand Visual Studio was complaining that it couldn't deduce template argument Func... and I don't know why...

I don't know what was going on... maybe Visual Studio was acting up... It appears to be working now... very odd...

1

There are 1 best solutions below

15
On BEST ANSWER

First of all, the below syntax:

template <typename T, typename U, template <U> class Func>

doesn't mean that Func will have a single type template argument, the same as the second template argument U of a Bar instance itself.

It means that Func is a class template that takes a non-type template parameter of type U. If Func requires a type template parameter, that should become:

template <typename T, typename U, template <typename> class Func>
//                                          ~~~~~~~^

And a matching deduction guide:

template <typename T, typename U, template <typename> class Func>
Bar(Foo<T>, U, Func<U>) -> Bar<T, U, Func>;

However, Func remains a template template parameter, and accepts only alias/class/struct templates, and that will never match a function pointer type, nor a lambda expression. If you indend to store any callable object inside Bar instances, then use any type as a template parameter, and let the deduction guide deduce which is it:

template <typename T, typename U, typename Func>
//                                ~~~~~~~^

In order to make sure that it will be callable with an (lvalue) argument of type U, just put a constraint like a static_assert:

#include <type_traits>

template <typename T, typename U, typename Func>
class Bar {
    static_assert(std::is_invocable_v<Func&, U&>, "Invalid callable");
private:
    Foo<T> foo_;
    Func func_;
    U n_;
public:
    Bar(Foo<T> foo, U n, Func func) :
      foo_{foo},
      func_{func},
      n_{n}
    {}
};

DEMO

Also note that you don't need an explicit deduction guide, as one will be generated implicitly from the constructor.


However, if you don't know in advance what U will be used as an argument to Func, then that shouldn't be considered as a problem in the constructor definition, nor in the class definition itself. It's a clear indication that the argument will be supplied from some external source, and at some place you will know and will be able to verify whether it fits the callable or not.

For sure, you should not be trying to deduce the exact signature of a callable object. It's useless in practice and most probably means there's a flaw in your design.

That is, once you eventually know what type of an argument is used, put a static_assert there, e.g.:

template <typename ValueType>
constexpr auto calculate(ValueType dx) {
    static_assert(std::is_invocable_v<Func&, ValueType&>, "Invalid value type");
    ValueType result = 0.0;
    // ...
    return result;
}

Alternatively, you can make calculate SFINAE-friendly with std::enable_if_t or requires:

template <typename ValueType>
constexpr auto calculate(ValueType dx)
    -> std::enable_if_t<std::is_invocable_v<Func&, ValueType&>, ValueType> {
    ValueType result = 0.0;
    // ...
    return result;
}