How can I move decision to compile time?

Hi,

I have this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template < class TargetUnits, class SourceUnits>
struct factor
{
private:
	typedef  ratio_divide<TargetUnits, SourceUnits> r;
public:
	static long double convertToSmaller(long double source)
	{
		if (r::num >= r::den)
		{
			source *= r::den;
			source /= r::num;
			return source;
		}
		else
		{
			source *= r::num;
			source /= r::den;
			return source;
	       }
	}
};


Since r is known at instantiation time, it seems incorrect to execute the if at runtime. This should be a decision to be made at compile time. How can I accomplish this?

Best regards,
Juan
I would assume an optimizing compiler would already be culling the unreachable branch at compile time. Is that not happening?

[edit: To do so explicitly, maybe use std::enable_if with two different function implementations.]
Last edited on
Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template < class TargetUnits, class SourceUnits >
struct factor
{

private:
	typedef  ratio_divide<TargetUnits, SourceUnits> r;
	
public:
        
        // unless we are cross-compiling, good idea to make it constexpr
        static constexpr long double convertToSmaller( long double source )
        { return convert_helper< ( r::num >= r::den ) >::convert(source) ; }

private:

	template < bool, typename = void  > struct convert_helper
	{
	    static constexpr long double convert( long double source )
	    { return ( source * r::den ) / r::num ; }
	};

	template < typename T  > struct convert_helper< false, T >
	{
	    static constexpr long double convert( long double source )
	    { return ( source * r::num ) / r::den ; }
	};
};
Will check the optimization ... yet I think it better to be done by meta-programming..

JLBorges, as usual, a great idea, will try it out ASAP...


Thanks both of you,
Juan Dent
BTW JLBorges, I was trying an idea along these lines: (excuse me, I know this doesn't work -- just trying something via enable_if):

I am missing something vital here: how can I make enable_if "enable only the one function whose boolean expression is true"??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename = enable_if<r::num >= r::den>::type >
static long double convertToSmallerUnit(long double source)
{
		source *= r::den;
		source /= r::num;
		return source;
}
template<typename = enable_if<r::num < r::den>::type >
static long double convertToSmallerUnit(long double source)
{
		source *= r::num;
		source /= r::den;
		return source;
}


but it gives me ambiguous overloads, which should not happen since in any particular instantiation, only one of the functions would be present, right?


Regards,
Juan
Last edited on
std::enable_if relies on SFINAE; on discarding functions from the overload resolution set if there is a substitution failure when a deduced type is substituted for a template parameter.

In this particular case, there is no type deduction; the only argument is known to be of type long double. For SFINAE to come into play, we would have to forward the call to a helper function which takes an extra argument, for which the type is deduced.

For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template < class TargetUnits, class SourceUnits >
struct factor
{

private:
	typedef  ratio_divide<TargetUnits, SourceUnits> r;
	
public:

    static constexpr long double convertToSmaller( long double source )
    { return convert_helper( source, r{} ) ; }

private:

    template < typename R >
    static constexpr typename std::enable_if<  R::num >= R::den, long double >::type
    convert_helper( long double source, R r ) { return ( source * r::den ) / r::num ; }

    template < typename R >
    static constexpr typename std::enable_if<  R::num < R::den, long double >::type
    convert_helper( long double source, R r ) { return ( source * r::den ) / r::num ; }
};


It would perhaps be cleaner if we use simple tag-dispach for resolving the overload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
public:

    static constexpr long double convertToSmaller( long double source )
    { return convert_helper( source, std::integral_constant< bool, ( r::num >= r::den ) >{} ) ; }

private:

    static constexpr long double convert_helper( long double source, std::true_type )
    { return ( source * r::den ) / r::num ; }

    static constexpr long double convert_helper( long double source, std::false_type )
    { return ( source * r::num ) / r::den ; }
};
conditional compiles with # statements still work.
you define r1, its the first case, else its the second.

#ifdef r1
source *= r::den;
source /= r::num;
return source;
#else
source *= r::num;
source /= r::den;
return source;

#endif

you can do some funky performance tweaking with the old C stuff...

void foo()
{
stuff;
#include file with inline code!
otherstuff;
#include file with inline code!
}

you just inlined 2 function calls, no matter what the compiler wanted to do instead :)


Hi JLBorjes!! Great again!!!

Just one detail: in your first solution, we have to create an object (r{}), so I don't think its more efficient than a runtime if as in my first approach (being Very Picky here!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static long double convertToSmaller(long double source)
	{
		if (r::num >= r::den)
		{
			source *= r::den;
			source /= r::num;
			return source;
		}
		else
		{
			source *= r::num;
			source /= r::den;
			return source;
	       }
	}


Of course it shows me why SFINAE wasn't working for me!!

Your second (tag-dispatch) solution is I think the most efficient and perhaps clearer!


Please receive my absolute regards for your brilliant and consistent help!

I am relatively knew to modern C++ and I must thank you...

Juan Dent
Tag dispatch also creates an object (the object is the tag).
Tough in both cases, the as-if rule would permit optimising away the creation of the object.
(You can verify that in both cases, if the argument is a constexpr, the function is evaluated at compile-time.)

But yes; in general, code involving tag dispatch tends to be cleaner than techniques using SFINAE.
The original code (with the if) does not involve creation of any secondary object.
To force compile time evaluation, all we need to do is change that to an arithmetic if and remove modifications of objects in the evaluation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <ratio>

template < unsigned int a, unsigned int b > struct A
{
    static_assert( a != 0 && b != 0, "non-zero value expected" ) ;
    using r = std::ratio<a,b> ;

    static constexpr int foo( double v )
    { return r::den > r::num ? ( v * r::num / r::den ) : ( v * r::den / r::num ) ; }
};

int main()
{
    switch( sizeof(int) )
    {
        case A<4,2>::foo(8.2) : std::cout << "A<4,2>::foo(8.2) was evaluated at compile time\n" ; break ;

        default: ;
    }
}

http://coliru.stacked-crooked.com/a/0ce7ecebd3e47a57

Note: C++17 has constexpr if statement http://en.cppreference.com/w/cpp/language/if
which would come in handy for more complex evaluations.
Topic archived. No new replies allowed.