Руководство по вычету для параметра шаблона шаблона

У меня есть набор класса структуры как таковой:

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

И я пытаюсь создать руководство по дедукции для этого шаблона класса...

// 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>;

Я не уверен в правильном синтаксисе для этого, когда аргумент шаблона является самим шаблоном, где этот шаблонный аргумент будет указателем на функцию, объектом функции, функтором или лямбдой, которую будет хранить класс.

Когда я пытаюсь использовать U в Func<>в нем говорится "type nameне допускается ", и если я удалю его, чтобыFuncбез каких-либо аргументов шаблона он утверждает, что "отсутствует список аргументов для параметра шаблона шаблона 'Func'"...

Мое предполагаемое использование Bar выглядит так:

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


РЕДАКТИРОВАТЬ - Этот раздел предназначен для пользователя: Piotr Skotnicki

Примечание. Вышеупомянутый был псевдокодом с теми же сигнатурами, что и представление моих классов... Теперь, когда у меня снова есть доступ к моей IDE, вот "настоящий" источник.

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

Ранее Visual Studio жаловалась, что не может вывести аргумент шаблона Func... и я не знаю почему...

Я не знаю, что происходит... может быть, Visual Studio шутила... Кажется, сейчас работает... очень странно...

1 ответ

Решение

Прежде всего, приведенный ниже синтаксис:

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

это не значит Funcбудет иметь аргумент шаблона одного типа, такой же, как и второй аргумент шаблонаU из Bar сам экземпляр.

Это означает, что Func- это шаблон класса, который принимает не типовой параметр шаблона типаU. ЕслиFunc требуется параметр шаблона типа, который должен выглядеть следующим образом:

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

И соответствующее руководство по вычету:

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

Однако, Funcостается параметром шаблона шаблона и принимает только шаблоны псевдонимов / классов / структур, которые никогда не будут соответствовать ни типу указателя функции, ни лямбда-выражению. Если вы хотите сохранить любой вызываемый объект внутриBar экземпляров, затем используйте любой тип в качестве параметра шаблона и позвольте руководству по дедукции определить, что это такое:

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

Чтобы убедиться, что он будет вызываться с аргументом (lvalue) типаU, просто поставьте ограничение, например 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}
    {}
};

ДЕМО

Также обратите внимание, что вам не нужно явное руководство по дедукции, так как оно будет неявно сгенерировано из конструктора.


Однако, если вы заранее не знаете, что U будет использоваться в качестве аргумента Func, то это не следует рассматривать как проблему ни в определении конструктора, ни в самом определении класса. Это явное указание на то, что аргумент будет предоставлен из какого-то внешнего источника, и в каком-то месте вы узнаете и сможете проверить, подходит ли он вызываемому объекту или нет.

Конечно, вам не следует пытаться определить точную подпись вызываемого объекта. На практике это бесполезно и, скорее всего, означает, что в вашем дизайне есть изъян.

То есть, когда вы в конце концов узнаете, какой тип аргумента используется, поставьте static_assert там, например:

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

В качестве альтернативы вы можете сделать calculate SFINAE-дружелюбный с std::enable_if_t или 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;
}
Другие вопросы по тегам