正确声明模板参数 - 当函数定义被明确专门化时的参数

时间:2018-03-21 13:38:01

标签: c++ templates variadic-functions c++17 overloading

我有一个看起来像这样的课:Foo是我的班级; FooBar是一组来自库的不同类型的类,每个类都有独立的名称。

foo.h中

class Foo {
public:
    Foo() = default;

    // There many types, and many of these types have multiple constructors
    // All of the appropriate overloads are available here.
    template<class Type>
    FooBar<Type>& getFooBarByFullName( ... ) {
       // construct & return FooBar<Type>(...);
    }      

    // Then I have a hand full of overloaded function template declarations 
    // with a generic name to call the appropriate functions from above.
    // Each FooBar has different parameters, and FooBar is a class template.
    template<class Type, template<typename> class FooBar>
    FooBar<Type>&  getFooBar(...);
};

// Outside of any class I have a generic function template
template<class Other, class Type, template<typename> class FooBar, class... FP>
Type doSomething( some param A, some param B, some param C, FP... params ) {
    // Code here to work with Other using A, B & C

    FooBar<Type> fooBar = getFooBar<Type, FooBar>( params... );
    // do something to fooBar

    return value generated from fooBar;
}

Foo.cpp中

#include **Foo.h**

template<class Type, template<typename> class FooBar>
FooBar<Type>&  getFooBar(...) {
    return {};
}

template<>
FooBar<int>& Foo::getFooBar( ... ) {
    return getFooBarByFullName( ... );
}

template<>
FooBar<short>& Foo::getFooBar( ... ) {
    return getFooBarByFullName( ... );
}

// etc...

我正在处理的其中一个模板参数的实现之一是class unary_op

我不想定义任何这样的类。我需要能够将函数对象,函数指针,lambda或std :: function作为unary_op类传递给这些函数。

我遇到的问题是我的标题中的声明是否如下所示:

template<class IntType = int, class UnaryOp>
FooBar<IntType>& getFooBarByFullName( std::size_t count, double xmin, double xmax, UnaryOp fw ) {
    // Constructors last parameter is defined as a class UnaryOp; 
    // but can be any of a function object, function pointer, lambda, std::function<...> etc.
    FooBar<IntType> fooBar( count, xmin, xmax, fw ); 
}

// Then I can declare the appropriate generic declaration overload here
template<class Type, template<typename> class FooBar, class FuncOp>
FooBar<Type>& getFooBar( std::size_t count, double xmin, double xmax, FuncOp fw ); // Declaration only

然而,当我去cpp文件中使用提供的适当重载声明来编写定义显式特化时,同时试图避免歧义是我遇到麻烦的地方。

template<>
FooBar<int>& Foo::getFooBar( std::size_t count, double xmin, double xmax, ? ) {
    return getFooBarByFullName<int>( count, xmin, xmax, ? );
}

template<>
FooBar<short>& Foo:getFooBar( std::size_t count, double xmin, double xmax, ? ) {
    return getFooBarByFullName<short>( count, xmin, xmax, ? );
}

如您所见,我不知道如何定义class UnaryOp类型的最后一个参数。我还希望能够支持调用者可以传递我上面提到的任何类型:function objectfunction pointerlambdastd::function<>作为最后一个参数UnaryOp。我不知道从哪里开始......

编辑 - 我忘记在我的实际代码中提到它;上面的两个类删除了默认构造函数;并且所有类方法都是静态的。

2 个答案:

答案 0 :(得分:1)

目前还不清楚你究竟在问什么,但看起来你的问题是在你的.cpp文件中创建一个可实例化但通用的函数。我认为有两种方法可以解决这个问题:

  1. 放弃您的计划:使这些方法模板仅生成.hpp文件并将UnaryOp作为(可推导)模板参数。

    .hpp:
    template<typename Type, typename UnaryOp>
    Type Qoo(Type const&x, UnaryOp&&func)
    {
       // some simple/short code calling func()
    }
    
  2. 在.cpp文件中为UnaryOp = std::function实现函数重载,并在您的.cpp中将常规UnaryOp(lambda,functor,函数指针等)实现为模板。 hpp文件,使用从std::function创建的UnaryOp对象调用前者。

    .hpp:
    template<typename Type>
    Type Qoo(Type const&, std::function<Type(Type)>&&);
    
    template<typename Type, typename UnaryOp>
    Type Qoo(Type const&x, UnaryOp&&func)
    {
       return Qoo(x, std::function<Type(Type)>{func});
    }
    
    .cpp
    template<typename Type>
    Type Qoo(Type const&t, std::function<Type(Type)>&&func);
    {
       // some lengthy code calling func()
    }
    // explicit instantiations
    template int Qoo(int const&, std::function<int(int)>&&);
    template short Qoo(short const&, std::function<short(short)>&&);
    ...
    
  3. 第二个版本允许预编译,但在UnaryOpstd::function<>的情况下会产生开销。 第一种解决方案避免了这种开销,但将完整的实现暴露给.hpp文件,并没有提供预编译的好处。

    在类似情况下,如果实现的代码很大,我倾向于使用第二个版本,这样可以容忍std::function对象的开销,而第一个版本只适用于小代码,通常应该是无论如何inline

    最后,请注意,在.cpp文件中,您不需要定义所有特化,而是提供模板并指定显式实例化。

答案 1 :(得分:0)

好吧废弃上面的整个想法:我把我的课程完全重写为一个单独的课程。该类本身现在是一个类模板。它看起来像这样:

#ifndef GENERATOR_H
#define GENERATOR_H

#include <limits>
#include <chrono>
#include <random>
#include <type_traits>

enum SeedType { USE_CHRONO_CLOCK, USE_RANDOM_DEVICE, USE_SEED_VALUE, USE_SEED_SEQ };

template<class Engine, class Type, template<typename> class Distribution>
class Generator {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
        std::chrono::high_resolution_clock,
        std::chrono::steady_clock>;

private:
    Engine _engine;
    Distribution<Type> _distribution;
    Type _value;

public:

    template<class... Params>
    explicit Generator( Engine engine, Params... params ) : _engine( engine ) {
        _distribution = Distribution<Type>( params... );
    }

    void seed( SeedType type = USE_RANDOM_DEVICE, std::size_t seedValue = 0, std::initializer_list<std::size_t> list = {} ) {
        switch( type ) {
            case USE_CHRONO_CLOCK:  { _engine.seed( getTimeNow() );  break; }
            case USE_RANDOM_DEVICE: { std::random_device device{};
                                      _engine.seed( device() );      break; }
            case USE_SEED_VALUE:    { _engine.seed( seedValue );     break; }
            case USE_SEED_SEQ:      { std::seed_seq seq( list );
                                      _engine.seed( seq );           break; }
        }
    }

    void generate() {
        _value = _distribution( _engine );
    }

    Type getGeneratedValue() const {
        return _value;
    }

    Distribution<Type> getDistribution() const {
        return _distribution;
    }

    std::size_t getTimeNow() {
        std::size_t now = static_cast<std::size_t>(Clock::now().time_since_epoch().count());
        return now;
    }

};

#endif // !GENERATOR_H

使用它就像:

#include <iostream>
#include <iomanip>
#include <vector>
#include "generator.h"

int main() {

    // Engine, Seeding Type, & Distribution Combo 1
    std::mt19937 engine1;
    Generator<std::mt19937, short, std::uniform_int_distribution> g1( engine1, 1, 100 );
    g1.seed( USE_RANDOM_DEVICE );
    std::vector<short> vals1;
    for( unsigned int i = 0; i < 200; i++ ) {
        g1.generate();
        auto v = g1.getGeneratedValue();
        vals1.push_back( v );
    }

    int i = 0;
    for( auto& v : vals1 ) {

        if( (i % 10) != 0 ) {
            std::cout << std::setw( 3 ) << v << " ";
        } else {
            std::cout << '\n' << std::setw( 3 ) << v << " ";
        }       
        i++;
    }
    std::cout << "\n\n";

    // Engine, Seeding Type, & Distribution Combo 2
    std::ranlux48 engine2;
    std::initializer_list<std::size_t> list2{ 3, 7, 13, 17, 27, 31, 43 };

    Generator<std::ranlux48, unsigned, std::binomial_distribution> g2( engine2, 50, 0.75 );
    g2.seed( USE_SEED_SEQ, std::size_t(7), list2 );

    std::vector<unsigned> vals2;

    for( int i = 0; i < 200; i++ ) {
        g2.generate();
        auto v = g2.getGeneratedValue();
        vals2.push_back( v );
    }

    for( auto& v : vals2 ) {

        if( (i % 10) != 0 ) {
            std::cout << std::setw( 3 ) << v << " ";
        } else {
            std::cout << '\n' << std::setw( 3 ) << v << " ";
        }
        i++;
    }
    std::cout << "\n\n";

    // Engine, Seeding Type, & Distribution Combo 3
    std::minstd_rand engine3;
    Generator<std::minstd_rand, float, std::gamma_distribution> g3( engine3, 0.22222f, 0.7959753f );
    g3.seed( USE_CHRONO_CLOCK );

    std::vector<float> vals3;

    for( int i = 0; i < 200; i++ ) {
        g3.generate();
        auto v = g3.getGeneratedValue();
        vals3.push_back( v );
    }

    for( auto& v : vals3 ) {

        if( (i % 5 ) != 0 ) {
            std::cout << std::setw( 12 ) << v << " ";
        } else {
            std::cout << '\n' << std::setw( 12 ) << v << " ";
        }
        i++;
    }
    std::cout << "\n\n";


    std::cout << "\nPress any key and enter to quit.\n";
    std::cin.get();

    return 0;
}