Fun with Templates

I have embarked on an effort to create some fairly complex template classes that involve template functions or functors used as class parameters. As soon as I realized that I was beginning to swim in an ocean of confusing syntax, I decided to back up and take things one step at a time. (That's actually advice I've given to others on this forum. It's good to listen to one's own advice.) So, I backed away from the outer class and am focusing initially on just a template function.

The question I have at the present time relates to a template function taking either a template function or a template functor object as an argument (based on a pattern I found on the interwebs). This code works as expected.

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
28
29
30
#include <iostream>
#include <iomanip>
#include <functional>

template <typename T>
bool myEqualTo(const T& lhs, const T& rhs)
{
	return (lhs == rhs);
}

template <typename T, typename F>
bool doOp(F f, const T& lhs, const T& rhs)
{
	return f(lhs, rhs);
}

int main()
{
	int rhs = 10;
	int lhs = 10;
	std::cout << std::boolalpha;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(myEqualTo<int>, lhs, rhs) << std::endl;

	lhs = 5;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(std::equal_to<int>(), lhs, rhs) << std::endl;
}




Question: I would like to see if I can allow doOp to assume that F is a template function/functor and infer its arugment type from the T type passed in as arguments lhs and rhs. I think this would allow me to get rid of the <int> template argument in the calls to doOp. However, I have made various attempts and failed each time. Is it possible to do what I am attempting? If so, what is the syntax?

Here is one of my latest attempts.

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
28
29
30
#include <iostream>
#include <iomanip>
#include <functional>

template <typename T>
bool myEqualTo(const T& lhs, const T& rhs)
{
	return (lhs == rhs);
}

template <typename T, template <typename> typename F >
bool doOp(F<T> f, const T& lhs, const T& rhs)
{
	return f(lhs, rhs);
}

int main()
{
	int rhs = 10;
	int lhs = 10;
	std::cout << std::boolalpha;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(myEqualTo, lhs, rhs) << std::endl;

	lhs = 5;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(std::equal_to, lhs, rhs) << std::endl;
}


The errors I am getting are:


testTemp3DoOp.cpp:11:52: error: expected 'class' before 'F'
 template <typename T, template <typename> typename F >
                                                    ^
testTemp3DoOp.cpp: In function 'int main()':
testTemp3DoOp.cpp:24:27: error: no matching function for call to 'doOp(<unresolved overloaded function type>, int&, int&)'
   doOp(myEqualTo, lhs, rhs) << std::endl;
                           ^
testTemp3DoOp.cpp:24:27: note: candidate is:
testTemp3DoOp.cpp:12:6: note: template<class T, template<class> class F> bool doOp(F<T>, const T&, const T&)
 bool doOp(F<T> f, const T& lhs, const T& rhs)
      ^
testTemp3DoOp.cpp:12:6: note:   template argument deduction/substitution failed:
testTemp3DoOp.cpp:24:27: note:   couldn't deduce template parameter 'template<class> class F'
   doOp(myEqualTo, lhs, rhs) << std::endl;
                           ^
testTemp3DoOp.cpp:29:21: error: missing template arguments before ',' token
   doOp(std::equal_to, lhs, rhs) << std::endl;
                     ^




When I figure out what I can and cannot do with these template argument, I will move on to my next step. Stay tuned.
Last edited on
It's not possible with myEqualTo() being a function, since the signature must be fully resolved at the call site, since the code generated for the function depends on the function's signature. In other words, you're trying to invert the type deduction order.

This works, although it's not quite what you wanted:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct myEqualTo{
	template <typename T>
	static bool f(const T& lhs, const T& rhs)
	{
		return (lhs == rhs);
	}
};

template <typename F, typename T>
bool doOp(const T& lhs, const T& rhs)
{
	return F::f(lhs, rhs);
}
One stupid quirk of the grammar is that template template arguments must use the keyword class.

For example, a program can't say
1
2
template <template <typename> typename F>
void f();

But has to say instead
1
2
template <template <typename> class F>
void f();

This is fixed in C++17.

The other issue is that it is not possible to specify function templates as template template arguments. The workaround for this is to wrap function templates in a class:
1
2
3
4
5
6
7
8
9
constexpr struct 
{
  // TODO: add perfect forwarding and SFINAE
  template <typename T, typename U>
  constexpr auto operator()(T t, U u) const
  { 
    return t == u;
  }
} myEqualTo;


Since C++14's transparent operator functor proposal, the types std::equal_to<void>, std::less<void>, etc., take this structure by default - although the functionality was added in a backwards-compatible way.

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
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <iomanip>
#include <functional>

constexpr struct 
{
  // TODO: add perfect forwarding and SFINAE
  template <typename T, typename U>
  constexpr auto operator()(T t, U u) const
  { 
    return t == u;
  }
} myEqualTo;

template <typename T, typename U, typename F>
bool doOp(F f, const T& lhs, const U& rhs)
{
	return f(lhs, rhs);
}

int main()
{
	int rhs = 10;
	int lhs = 10;
	std::cout << std::boolalpha;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(myEqualTo, lhs, rhs) << std::endl;

	lhs = 5;
    
        // std::equal_to<> is std::equal_to<void>; there's a 
        //   default template argument
	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(std::equal_to<>{}, lhs, rhs) << std::endl;
}

http://coliru.stacked-crooked.com/a/5fbc4a248e3a3489
Last edited on
@helios, @mbozzi,

Thank-you for your responses. I had a feeling I wasn't going to be able to do what I was trying to do, specifically with the function. Thank-you both for clarifying it.

It looks like the myEqualTo struct that you both suggested to me is essentially the same thing as std::equal_to. Let me know if I'm missing some sort of structural difference that I'm not seeing. (Obviously you were modifying my code where I was trying to use a template function to mimic the std::equal_to struct, so this is no surprise.)

What is the difference between @helios' struct myEqualTo {} and @mbozzi's constexpr struct {} myEqualTo? [Edit: I don't mean the 2nd template argument, just the use of constexpr] I'm only vaguely familiar with constexpr and don't quite understand how it works in a situation like this.

I will continue to play with what you have given me and add to my project. I'm sure I will have more syntax issues tomorrow.
Last edited on
It looks like the myEqualTo struct that you both suggested to me is essentially the same thing as std::equal_to.

Basically, yeah. constexpr struct {} myEqualTo declares an object similar to std::equal_to<void> x{}.

If struct A {} a; declares an object a of type A, then struct {} b; declares an object b of some unnamed class type. constexpr struct {} c; does the same, but the constexpr means c can appear in constant expressions (e.g., in array bounds), and confers internal linkage, so that if the definition of c appeared in a header file, it wouldn't violate the One Definition Rule.

Very roughly, constexpr is a request to the compiler to "run this code when compiling, when possible". The current definition lets me say myEqualTo(2, 2) and compute true before the program even runs, every single time.

If it helps, you can remove it: constexpr isn't necessary in this case.
Last edited on
Ah, so you declared a global instance so you could pass it without modifying the prototype of doOp() and use operator()(). I didn't think of that one. I thought about instantiating a temporary, but I didn't like how its usage looked.
Well, I had to take a break to do things that I had to do. Now I'm back to things that I want to do.

I went back and forth playing with both ideas, and decided to go with something similar to @mbozzi's suggestion. I decided use an object of a non-template class for my comparison so that I am not required to create a template functor for a user-defined type. Unfortunately, I do not yet have C++14 available to me yet (although maybe soon), so I was not able to play with the transparent operator functors.

I ended up with this.

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
28
29
30
31
32
33
#include <iostream>
#include <iomanip>
#include <functional>

struct MyEqualTo
{
	template <typename T>
	bool operator()(const T& lhs, const T& rhs) const
	{
		return (lhs == rhs);
	}
};

template <class F, typename T>
bool doOp(F f, const T& lhs, const T& rhs)
{
	return f(lhs, rhs);
}

int main()
{
	int rhs = 10;
	int lhs = 10;
	std::cout << std::boolalpha;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(MyEqualTo{}, lhs, rhs) << std::endl;

	lhs = 7;

	std::cout << "Is " << lhs << " == " << rhs << "? " <<
		doOp(std::equal_to<int>{}, lhs, rhs) << std::endl;
}



So, I moved on to my next step of wrapping the values to compare into a class with the comparison object as a template. Essentially it's the same as the doOp version, but the arguments and the comparison object are passed into the constructor and the comparison is made via the compare() member function. The results are here:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <iomanip>
#include <functional>

struct MyEqualTo
{
	template <typename T>
	bool operator()(const T& lhs, const T& rhs)
	{
		return (lhs == rhs);
	}
};


template <class F, typename T>
class CompareObject
{
public:
	CompareObject(F f, const T& targetValue, const T& initValue) :
			f(f), target(targetValue), val(initValue) {}

	bool compare() { return f(val, target); }
	void setValue(const T& newValue) { val = newValue; }

private:
	F f;
	const T target;
	T val;
};


int main()
{
	std::cout << std::boolalpha;

	//CompareObject oc1(std::equal_to<int>{}, 10, 0);
	CompareObject<std::equal_to<int>, int> oc1(std::equal_to<int>{}, 10, 0);
	std::cout << "Is 10 == 0? " << oc1.compare() << std::endl;

	oc1.setValue(10);
	std::cout << "Is 10 == 10? " << oc1.compare() << std::endl;

	//CompareObject oc2(MyEqualTo{}, 5, 0);
	CompareObject<MyEqualTo, int> oc2(MyEqualTo{}, 5, 0);
	std::cout << "Is 5 == 0? " << oc2.compare() << std::endl;

	oc2.setValue(5);
	std::cout << "Is 5 == 5? " << oc2.compare() << std::endl;

	return 0;
}


My question is why the commented out lines do not compile.

testFunctor.cpp: In function 'int main()':
testFunctor.cpp:36:16: error: missing template arguments before 'oc1'
  CompareObject oc1(std::equal_to<int>{}, 10, 0);


In the doOp version above, I do not need to specify the template arguments to the template function. Why do I need to specify the template arguments to my CompareObject class when calling the constructor? It's not a deal breaker or anything--I would just like to know why.

My question is why the commented out lines do not compile

There's no good reason that I know of, it's just not in the language. Until C++17:
https://en.cppreference.com/w/cpp/language/class_template_argument_deduction

The workaround for this is to use function template argument deduction instead:
1
2
3
4
5
template <typename T, typename Fn>
CompareObject<Fn, T> make_CompareObject(Fn f, T const&target, T const &init)
{
    return CompareObject<Fn, T>(f, target, init);   
}

Be aware that the types of the second and third arguments have to match, or the deduction will fail.

For example
 
make_CompareObject(MyEqualTo{}, 0, 0L); // 0 is int; 0L is long 

Then one might expect T to be long, but instead the result is a compiler error.

For this reason, we've put typename T first in the template parameter list, so that we can say
auto oc2 = make_CompareObject<long>(MyEqualTo{}, 5, 0L);
Instead of having to say the comparator type again (we might not be able to, if it's a lambda function):
auto oc2 = make_CompareObject<MyEqualTo, long>(MyEqualTo{}, 5, 0L);

This is actually a substantial problem, because it's really easy to say the wrong type, which causes logic errors. That's why the rule of thumb is "prefer not to specify template arguments", and partially why make_x() functions can be a good idea.
Last edited on
Topic archived. No new replies allowed.