1

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...

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • The `U` you're passing for `bar1` is an `int`. `funcA` is a `double(*)(double)`. That's not `F` for some `F`. This can't work... – Barry Jun 03 '20 at 04:05
  • @Barry, the `U` shouldn't be an `int` it should be the `double` or the `template argument` from the `function object` being passed in... this is where I'm getting stumped... – Francis Cugler Jun 03 '20 at 04:06
  • Well, `double(*)(double)` can't deduce to `F` either. You sure you don't want to just deduce a callable `F`? – Barry Jun 03 '20 at 04:07
  • Yeah, I was originally using `std::function` as this parameter before templating this class. Now I'm trying to template it, and to make it explicitly `constexpr`. My functions had the signature of `double func(double x)` but now they are templated as well. Single parameter only and returns the same type. – Francis Cugler Jun 03 '20 at 04:08
  • The last parameter to the class can be a function pointer, function object, functor or a lambda expression, of a single variable. The class will store it from its constructor and will invoke it within one of its member functions. – Francis Cugler Jun 03 '20 at 04:09
  • So just make it `typename F`. – Barry Jun 03 '20 at 04:10
  • If I just make it `typename F` then how do I retrieve the `type` from the `function object` that is passed in for this class's member functions? The function object that is passed is also being templated... I need to use that as part of the declaration to one of the member functions. Or would I just template that function itself? – Francis Cugler Jun 03 '20 at 04:16

1 Answers1

1

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;
}
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Your example is good, but the member `n_` is not `U`, it is `size_t`. The `T` is the type that the templated struct `Foo` will use, `U` is supposed to be the type that the `function objects`, `function pointers`, `functors`, `lambdas` will use when they are passed into the constructor, and this is needed for the return types of `Bar`s member functions... `U Bar::someInternalFunc()`. If that makes sense to you? They need to match in types. – Francis Cugler Jun 03 '20 at 11:45
  • However, this class doesn't know anything about those function objects until they are passed into the constructor... – Francis Cugler Jun 03 '20 at 11:50
  • 2
    @FrancisCugler I thought `size_t` was a typo, hence I suggested to use an implicit deduction guide. If it's not a typo, then just write a custom deduction guide. – Piotr Skotnicki Jun 03 '20 at 12:09
  • That's what I was trying to do, and I'm having a hard time getting it to work properly... I'm not completely familiar with the syntax... – Francis Cugler Jun 03 '20 at 12:22
  • @FrancisCugler Then what's the point in forcing `size_t` in the constructor when you actually want some `U` there ? – Piotr Skotnicki Jun 03 '20 at 12:24
  • I don't need the `U` in the constructor. It needs to be a part of the template for the class's members, their parameters and returns types. These types have to match the type of the templated function objects if that makes sense... – Francis Cugler Jun 03 '20 at 12:25
  • Bar has member functions that will both return a type and one of them takes a type, that type has to be the same from the templated function objects that are passed in such as `funcA` in the example. If funcA takes a double, then `Bar`'s member functions have to take that and will return that. – Francis Cugler Jun 03 '20 at 12:28
  • You seem to be missing one thing. You rarely need to know the signature of a callable object. All you actually want is to know if it is invocable with the arguments that you will supply. And you probably *do know* what are these arguments' types. – Piotr Skotnicki Jun 03 '20 at 12:28
  • Also, I wouldn't be able to use `std::is_invocable_v` without it. – Francis Cugler Jun 03 '20 at 12:29
  • 1
    @FrancisCugler Show me where and how you call `func_`. Otherwise it's just guessing what you actually want. – Piotr Skotnicki Jun 03 '20 at 12:40
  • I got my code to work in one scenario, but it fails in another... I'll add my code below the original part in an edit section. – Francis Cugler Jun 03 '20 at 12:41
  • For some reason, Visual Studio was failing to deduce the template argument for `Func`... I had removed the middle `template argument U` from the example above which was `ValueType` in my real code... Even after removing it, Visual Studio was still giving me the same compiler error. I then updated this post here. I switched from using the `templated function object` to just using a `function object`... to get the console results because I knew that version of it worked... Then when I used the templated type, to get the compiler error, it started to work... Very odd... – Francis Cugler Jun 03 '20 at 13:22
  • The only thing that I can think of, is maybe when I did a clean build and rebuilt the project it removed old stale code that was generating the compiler errors... So for over 2 hours last night, I was banging my head, changing code when I probably didn't have to... and never thought to go back to what I had from a previous point assuming that it wasn't working... I'm okay with templates, but there are some aspects of them that still trip me up at times... – Francis Cugler Jun 03 '20 at 13:23
  • 1
    @FrancisCugler Put `static_assert` in the `calculate` function. It's that function's problem if that function's argument does not match the stored callable obejct, not the fault of the class itself. – Piotr Skotnicki Jun 03 '20 at 13:42
  • Okay, that makes a lot more sense! – Francis Cugler Jun 03 '20 at 13:46