C ++将变量转换为模板参数

时间:2013-10-07 09:50:33

标签: c++ templates c++11

我想使用模板进行优化,如here所述。但是,随着bool模板参数的增加,实例化模板可能会有太多分支。如果你使用更大的枚举而不是bools,它会变得更加绚丽。

#include <iostream>
using namespace std;

template <bool b1, bool b2>
int HeavyLoop_impl(int arg)
{
    for (int i = 0; i < 10000000; i++)
    {
        // b1 is known at compile-time, so this branch will be eliminated
        if (b1) { arg += 1; }
        else    { arg += 2; }

        // b2 is known at compile-time, so this branch will be eliminated
        if (b2) { arg += 10; }
        else    { arg += 20; }
    }
    return arg;
}

// This function could be generated automatically
void HeavyLoop(bool b1, bool b2, int arg)
{
    int res;
    if (b1) {
        if (b2) { res = HeavyLoop_impl<true, true>(arg); }
        else    { res = HeavyLoop_impl<true, false>(arg); }
    } else {
        if (b2) { res = HeavyLoop_impl<false, true>(arg); }
        else    { res = HeavyLoop_impl<false, false>(arg); }
    }
    cout << "res: "<<res<<endl;
}

int main(int argc, char**argv)
{
    bool b1 = true;
    bool b2 = false;
    int arg = 0;
    HeavyLoop(b1, b2, arg);
    return 0;
}

有没有办法自动生成HeavyLoop功能?我想要这样的东西:

vars_to_template_function<bool, bool>(HeavyLoop_impl, b1, b2, arg);

这会以某种方式成为可能吗?感谢任何提示。

注意:这只是一个非常简单的例子。实际循环当然更复杂:o)

6 个答案:

答案 0 :(得分:4)

我决定在代码中获得更多乐趣,这是我第一次尝试的改进版本,具有以下优点:

  • 支持enum种类型
  • 明确指定应转换的参数数量
  • 复杂部分的通用实现,每个使用它的函数的一个小帮助程序。

代码:

#include <iostream>
#include <utility>
#include <type_traits>

// an enum we would like to support
enum class tribool { FALSE, TRUE, FILE_NOT_FOUND };

// declare basic generic template
// (independent of a specific function you'd like to call)
template< template< class > class CB, std::size_t N, typename = std::tuple<> >
struct var_to_template;

// register types that should be supported
template< template< class > class CB, std::size_t N, typename... Cs >
struct var_to_template< CB, N, std::tuple< Cs... > >
{
    // bool is pretty simple, there are only two values
    template< typename R, typename... Args >
    static R impl( bool b, Args&&... args )
    {
        return b
          ? var_to_template< CB, N-1, std::tuple< Cs..., std::true_type > >::template impl< R >( std::forward< Args >( args )... )
          : var_to_template< CB, N-1, std::tuple< Cs..., std::false_type > >::template impl< R >( std::forward< Args >( args )... );
    }

    // for each enum, you need to register all its values
    template< typename R, typename... Args >
    static R impl( tribool tb, Args&&... args )
    {
        switch( tb ) {
        case tribool::FALSE:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::FALSE > > >::template impl< R >( std::forward< Args >( args )... );
        case tribool::TRUE:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::TRUE > > >::template impl< R >( std::forward< Args >( args )... );
        case tribool::FILE_NOT_FOUND:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::FILE_NOT_FOUND > > >::template impl< R >( std::forward< Args >( args )... );
        }
        throw "unreachable";
    }

    // in theory you could also add int, long, ... but
    // you'd have to switch on every possible value that you want to support!
};

// terminate the recursion
template< template< class > class CB, typename... Cs >
struct var_to_template< CB, 0, std::tuple< Cs... > >
{
    template< typename R, typename... Args >
    static R impl( Args&&... args )
    {
        return CB< std::tuple< Cs... > >::template impl< R >( std::forward< Args >( args )... );
    }
};

// here's your function with the template parameters
template< bool B, tribool TB >
int HeavyLoop_impl( int arg )
{
    for( int i = 0; i < 10000000; i++ ) {
        arg += B ? 1 : 2;
        arg += ( TB == tribool::TRUE ) ? 10 : ( TB == tribool::FALSE ) ? 20 : 30;
    }
    return arg;
}

// a helper class, required once per function that you'd like to forward
template< typename > struct HeavyLoop_callback;
template< typename... Cs >
struct HeavyLoop_callback< std::tuple< Cs... > >
{
    template< typename R, typename... Args >
    static R impl( Args&&... args )
    {
        return HeavyLoop_impl< Cs::value... >( std::forward< Args >( args )... );
    }
};

// and here, everything comes together:
int HeavyLoop( bool b, tribool tb, int arg )
{
    // you provide the helper and the number of arguments
    // that should be converted to var_to_template<>
    // and you provide the return type to impl<>
    return var_to_template< HeavyLoop_callback, 2 >::impl< int >( b, tb, arg );
}

int main()
{
    bool b = true;
    tribool tb = tribool::FALSE;
    int arg = 0;
    int res = HeavyLoop( b, tb, arg );
    std::cout << "res: " << res << std::endl;
    return 0;
}

如果您想玩它,可以使用live example

答案 1 :(得分:3)

以下是您可以这样做的方法:

#include <iostream>
using namespace std;

template <bool b1, bool b2>
struct HeavyLoopImpl
{
    static int func(int arg)
    {
        for (int i = 0; i < 10000000; i++) {
            arg += b1 ? 1 : 2;
            arg += b2 ? 10 : 20;
        }
        return arg;
    }
};

template <template<bool...> class Impl,bool...Bs>
struct GenericJump
{
    template<typename... Args>
    static int impl(Args&&... args)
    {
        return Impl<Bs...>::func(std::forward<Args>(args)...);
    }

    template<typename... Args>
    static int impl(bool b, Args&&... args)
    {
        return b
            ? GenericJump<Impl,Bs...,true >::impl(std::forward<Args>(args)...)
            : GenericJump<Impl,Bs...,false>::impl(std::forward<Args>(args)...);
    }
};

int HeavyLoop(bool b1, bool b2, int arg)
{
    return GenericJump<HeavyLoopImpl>::impl(b1,b2,arg);
}

int main()
{
    bool b1 = true;
    bool b2 = false;
    int arg = 0;
    int res = HeavyLoop(b1, b2, arg);
    cout << "res: "<<res<<endl;
    return 0;
}

这基本上是Daniels解决方案,但它允许您使用HeavyLoop_impl()以外的函数作为实现。只能调用单个模板函数才能达到通用解决方案的目的。 GenericJump模板类也可以调用其他函数。您只需将HeavyLoop_impl()模板函数更改为具有静态函数func()的模板类。它非常有效。它用gcc 4.7.3编译并给出正确的输出。

答案 2 :(得分:0)

您是否考虑过将函数作为模板参数传递?这样你就可以根据自己的意愿定制你的内循环。

Function passed as template argument

但是,函数调用的开销很小。

答案 3 :(得分:0)

理想的通用解决方案实际上取决于您想要改变的内容。变异的可能性是:

  • 分支数量以及要分支的变量类型。
  • 要执行的操作及其参数的数量和类型。

我建议不要制作完全通用的解决方案,除非你真的需要改变所有这些。只考虑你想改变的事情,你的生活会更容易。

假设分支总数小于2 ^ 64,您可以使用switch语句进行分派。以下解决方案演示了这可以如何工作:

template<unsigned permutation>
struct Permutation
{
    static_assert(permutation < 4, "permutation must be in the range [0, 4)");
    static const bool b1 = permutation & (1 << 0);
    static const bool b2 = permutation & (1 << 1);
};

unsigned makePermutation(bool b1, bool b2)
{
    return (b1 << 0) | (b2 << 1);
}

template<unsigned p>
int HeavyLoop_impl(int arg)
{
    return HeavyLoop_impl<Permutation<p>::b1, Permutation<p>::b2>(arg);
}

int HeavyLoop_impl(unsigned permutation, int arg)
{
    switch(permutation)
    {
    case 0: return HeavyLoop_impl<0>(arg);
    case 1: return HeavyLoop_impl<1>(arg);
    case 2: return HeavyLoop_impl<2>(arg);
    case 3: return HeavyLoop_impl<3>(arg);
    }
}

[注意:使用Boost.Preprocessor生成上面的switch语句会很简单。]

void HeavyLoop(bool b1, bool b2, int arg)
{
    int res = HeavyLoop_impl(makePermutation(b1, b2), arg);
    cout << "res: "<<res<<endl;
}

答案 4 :(得分:0)

我认为你问题的最佳答案实际上并不是自动生成它,而是留下你在问题中已经拥有它的方式。

创建一个自动模板函数来生成中间地点只是混淆了你在一开始就做的不变切换。

我更倾向于尝试理解中间层在您的问题中的工作方式,而不是人们为您提供的任何答案。

我有一个类似的例子。在我的例子中,我可以在一组值之间应用许多不同的操作。阵列大小相等。但是,我还有一个结构,它使用影响我操作的权重值映射数组的子范围。 因此,例如,我可能正在使用100个值的数组,并且具有如下权重的范围:

[0,25] rangeWeight = 0
[26,35] rangeWeight = 0.25
[36,50] rangeWeight = 0.5
[51,99] rangeWeight = 1.0

所以每个操作看起来都像(伪):

for each subrange:
    alias to the dst buffer
    alias to the src buffer
    determine the number of elements in the range
    if there's any
        weight = weightPassedIn * rangeWeight;

        Op(dst, src, weight, numElements);

对我来说,有几个优化涉及目标是否被触及(如果它仍然处于清除值,可以做出一些假设来简化每个操作的数学运算),如果重量恰好是完整,1.0,还有其他快捷方式。

起初我用相同的设置一遍又一遍地编写相同的循环,一旦我将每个op周围的所有循环重构为函数,我几乎自然地拥有了你的不变包装器的形式。事实上,实际上有几个包装器主要用于包装循环内部发生的高级操作,否则只需处理这样的各个优化:

if (weight == 1.0f)
{
    if ( arrayIsCleared )
        Blend<BlendOpSet, true, false>(otherBuff, subRangesMask, 1.0f);
    else
        Blend<BlendOpAccumulate, true, false>(otherBuff, subRangesMask, 1.0f);
}
else
{
    if ( arrayIsCleared )
        Blend<BlendOpSet, false, false>(otherBuff, subRangesMask, weight);
    else
        Blend<BlendOpAccumulate, false, false>(otherBuff, subRangesMask, weight);
}

答案 5 :(得分:0)

这是boost :: hana的另一种解决方案,它还可以处理枚举:

import Page from 'components/Page';
import React from 'react';
import {
  Button,
  Card,
  CardBody,
  CardHeader,
  Col,
  FormFeedback,
  FormGroup,
  FormText,
  Input,
  Label,
  Row,
} from 'reactstrap';
import axios from 'axios';
import DataTable , { memoize } from 'react-data-table-component';
import { LoadingOverlay, Loader } from 'react-overlay-loader';
import 'react-overlay-loader/styles.css';
const columns = memoize(clickHandler => [
  {
    cell:(row) => <button onClick={clickHandler} id={row.ID}>Action</button>,
    ignoreRowClick: true,
    allowOverflow: true,
    button: true,
  },
  {
    name: 'ID',
    selector: 'ID',
    sortable: true,
    grow: 2,
  },
  {
    name: 'Name',
    selector: 'name',
    sortable: true,
    grow: 2,
  },
  {
    name: 'Class',
    selector: 'class',
    sortable: true,
  },

]);
const viewQuery ="SELECT ID,name,class FROM school ";
 class DTable extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            viewdata:{},
            loading:false,
            selectedRows: []

        };
      }
  viewData(){

    axios({
      method: 'post',
      url: '',
      data: {query:viewQuery},
      crossDomain: true,
      headers: {
          'Content-type': 'multipart/form-data'
        }
      })
      .then(response => {
         this.setState({ viewdata: response.data });

         //console.log(response)
        })
      .catch(function (response) {
          //handle error
          console.log(response);
      });
  } 
 componentDidMount() {
       this.viewData();

 }
 handleButtonClick = (state) => {
    console.log('clicked');
    console.log(state.target.id);
  };
  handleChange = state => {
    console.log('state', state.selectedRows);

    this.setState({ selectedRows: state.selectedRows });
  };
  render(){
  return (
    <Page title="Forms" breadcrumbs={[{ name: 'Forms', active: true }]}>
      <Row>
        <Col xl={7} lg={12} md={12}>
          <Card>
            <CardHeader>Input Types</CardHeader>
            <CardBody>
                <DataTable
                title="Created Form"

                data={this.state.viewdata}
                columns={columns(this.handleButtonClick)}
                onRowSelected={this.handleChange}
                selectableRows
                pagination
                dense
                />
            </CardBody>
          </Card>
        </Col>
      </Row>
    </Page>
  );
}
}
export default DTable;
#include <cstdio> #include <type_traits> #include <boost/hana.hpp> namespace hana = boost::hana; template <typename F, typename TArgs, typename TIn, typename TOut> void fun_arg_combinations_impl(F&& f, TArgs targs, TIn tin, TOut tout) { if constexpr (hana::is_empty(tin)) { hana::unpack(tout, f); } else { hana::for_each(hana::front(tin), [&](auto v){ if (v == hana::front(targs)) { fun_arg_combinations_impl(f, hana::drop_front(targs), hana::drop_front(tin), hana::append(tout, v)); } }); } } template <typename F, typename TArgs, typename TIn> void fun_arg_combinations(F&& f, TArgs targs, TIn tin) { fun_arg_combinations_impl(f, targs, tin, hana::tuple<>()); } enum Shape {LINE, CIRCLE, SQUARE}; int main() { auto f_heavy_loop = [](auto b1t, auto b2t, auto st) { constexpr bool b1 = decltype(b1t)::value; constexpr bool b2 = decltype(b2t)::value; constexpr Shape s = decltype(st )::value; printf("out:%d %d %d\n", b1, b2, s); }; //constexpr auto bools = hana::make_tuple(std::true_type{}, std::false_type{}); constexpr auto bools = hana::tuple<std::true_type, std::false_type>{}; constexpr auto shapes = hana::tuple< std::integral_constant<Shape, LINE>, std::integral_constant<Shape, CIRCLE>, std::integral_constant<Shape, SQUARE>>{}; // Using volatile to not allow the compiler to optimize for hard-coded values volatile bool b1 = true; volatile bool b2 = false; volatile Shape s = SQUARE; fun_arg_combinations( f_heavy_loop, hana::make_tuple(b1 , b2 , s ), hana::make_tuple(bools, bools, shapes)); } lambda中的

b1b2s均为f_heavy_loop(),因此我们可以在它们上使用constexpr

输出:

if constexpr

在这里查看生成的程序集:https://godbolt.org/z/nsF2l5