C++ Wrangling.
Nov. 29th, 2008 12:33 pmMy head hurts because I have been doing C++ metaprogramming for the last 3 days. I've been figuring out the actual rules relating to type deductions for template functions, and how they interact with SFINAE (Substitution Failure Is Not An Error -- except, of course, when it is).
Basically, what I am trying to do is create a geometric math library, called GUS, with a bunch of high end features that I've yet to see in any similar libraries. My first objective was to just have a definition of a vector template that took the right number of arguments. So that one could say:
gus::vector<int,3> v(1,3,6);
Thus, I needed a constructor that would take a number of parameters equal to the value passed into the template. My initial plan was to define my constructor as a member template and use type deduction to count the number of arguments passed in, and do reasonableness checking based on that.
I dug around and found out how type deduction for template functions works, and it turns out that there is a list of the allowable deductions, which are applied recursively to the argument types passed into a template function, and which are used to deconstruct the arguments for type (and other) information:
Thanks to an IM chat yesterday with
pphaneuf, I was put on the right path to getting my library written. It turns out that template members are only instantiated when called, so I could use macros to create constructors that took 0,1,2,3,... arguments and applied them in turn. Only the constructor with the right number of arguments would compile, but that wouldn't be a problem so long as no one ever called a constructor with the wrong number of arguments.
Given what he told me, I was able to get my stub library to compile and run correctly, but the results failed in the user friendly category. When one made a mistake in calling the library the C++ error messages were both obscure and unhelpful. Since this is a common problem with C++ error messages, it wasn't clear I could improve things, but I decided to give it a try.
Thats where SFINAE came in. If I created a bunch of member template constructors, and had all of them (except one) fail to substitute correctly, then rather than some obscure message about the inner guts of my library, a bad constructor call would produce an error saying something like 'wrong number of arguments for constructor', which would be much friendlier.
So, my first few (dozen) attempts all failed, because it turns out that the rules for when SFINAE works, and when it generates an error were not clear. I finally figured out, that for a working SFINAE failure in function templates:
There are still some issues in my stripped-down prototype that I need to work on, but I'm now confident that progress can start to be made at some sort of reasonable speed.
Basically, what I am trying to do is create a geometric math library, called GUS, with a bunch of high end features that I've yet to see in any similar libraries. My first objective was to just have a definition of a vector template that took the right number of arguments. So that one could say:
gus::vector<int,3> v(1,3,6);
Thus, I needed a constructor that would take a number of parameters equal to the value passed into the template. My initial plan was to define my constructor as a member template and use type deduction to count the number of arguments passed in, and do reasonableness checking based on that.
I dug around and found out how type deduction for template functions works, and it turns out that there is a list of the allowable deductions, which are applied recursively to the argument types passed into a template function, and which are used to deconstruct the arguments for type (and other) information:
More info is here. In any case, you'll note that there is absolutely no way to count arguments, so that idea was a bust. In fact its a bust in another way as well, because I hadn't realized that in a template function the number of arguments is part of the template signature, so its not possible to define a given template function that takes a differing number of arguments (without using '...' that is). So, I gave up on that approach.T const T volatile T T& T* T[9] A<T> C(*)(T) T(*)() T(*)(U) T C::* C T::* T U::* T (C::*)() C (T::*)() D (C::*)(T) C (T::*)(U) T (C::*)(U) T (U::*)() T (U::*)(V) E[9][i] B<i> TT<T> TT<i> TT<C>
- T, U, and V represent a template type argument
- 9 represents any integer constant
- i represents a template non-type argument
- [i] represents an array bound of a reference or pointer type, or a non-major array bound of a normal array.
- TT represents a template template argument
- (T), (U), and (V) represents an argument list that has at least one template type argument
- () represents an argument list that has no template arguments
- <T> represents a template argument list that has at least one template type argument
- <i> represents a template argument list that has at least one template non-type argument
- <C> represents a template argument list that has no template arguments dependent on a template parameter
Thanks to an IM chat yesterday with
Given what he told me, I was able to get my stub library to compile and run correctly, but the results failed in the user friendly category. When one made a mistake in calling the library the C++ error messages were both obscure and unhelpful. Since this is a common problem with C++ error messages, it wasn't clear I could improve things, but I decided to give it a try.
Thats where SFINAE came in. If I created a bunch of member template constructors, and had all of them (except one) fail to substitute correctly, then rather than some obscure message about the inner guts of my library, a bad constructor call would produce an error saying something like 'wrong number of arguments for constructor', which would be much friendlier.
So, my first few (dozen) attempts all failed, because it turns out that the rules for when SFINAE works, and when it generates an error were not clear. I finally figured out, that for a working SFINAE failure in function templates:
- The substitution failure must happen before the body of the function. That is, something has to fail in the type signature of the template function.
- The thing that fails must be dependant on the template parameter, or the compiler is allowed to decide its a static error.
- If you're using type deduction, when the thing that fails, fails to fail (as it were) it must do so in a way that allows for the type deduction to work.
There are still some issues in my stripped-down prototype that I need to work on, but I'm now confident that progress can start to be made at some sort of reasonable speed.
no subject
Date: 2008-11-29 06:35 pm (UTC)template
template<bool c, typename T = void> struct Provided;
template<> struct Provided<true, U> {typedef T U;};
Then you can write things sort of like
Foo(Provided<k==3, N>::T a, N b, N c) {...}
(waving hands about the details...)?
and
[internal] class Bar: private Provided<k==3> {...}
(Actually I thought I once had a simpler version of the same trick, which got the cleaner notation for the function case, too, but after reading your post it's clear that I've forgotten too much to make it work.)
Maybe I'm just confused, though, take with a pinch of salt.
no subject
Date: 2008-11-29 07:06 pm (UTC)no subject
Date: 2008-11-29 07:48 pm (UTC)gus::vector v(1,3,6);
And set the vector length equal to the number of arguments?
no subject
Date: 2008-11-29 07:53 pm (UTC)Doing it your way, the only way to tell if x+y was a valid vector expression would be to do an expensive run-time check.
no subject
Date: 2008-11-29 07:53 pm (UTC)no subject
Date: 2008-11-29 08:04 pm (UTC)You're trying to check for the case where the caller has specified the number of elements in two ways - as an explicit template parameter and by the number of parameters to the constructor - and they don't match. Unless you want to allow them to differ (for example, allowing some elements in the vector to be undefined or initialized with a default value) one of the two is redundant.
(Maybe you can't actually get that information to where it needs to be, though - I'm not a template expert.)
no subject
Date: 2008-11-29 08:19 pm (UTC)no subject
Date: 2008-11-29 10:14 pm (UTC)Whenever you want to pick a type depending on the parameters, what you need is a templated function (sometimes a templated method will help, but since one of the thing we want to decide is the type of the object itself, it's too late at that point). An example is std::tr1::bind (which I love dearly, and has a placeholder feature that could be used to provide the undefined/default elements thing you mentioned). It returns an std::tr1::function with the appropriate template parameters.
Now, since you still need to know the type it will return ahead of time, that's still not good enough. What you need is the upcoming auto keyword, which thankfully can be faked with a macro and typeof() (eww, but hey, it works). So you could have the following:
namespace gus {
template<class T, int num> class vector ... template<class T> gus::vector<T, 1> make_vector(T v1) { ... } template<class T> gus::vector<T, 2> make_vector(T v1, T v2) { ... } template<class T> gus::vector<T, 3> make_vector(T v1, T v2, T v3) { ... } } #ifdef CXX0X auto v = gus::make_vector(1, 3, 6); #else BOOST_AUTO(v, gus::vector(1, 3, 6)); #endifThe upcoming initializer lists would not help here, unfortunately, since the whole point is to vary the return type of make_vector.
no subject
Date: 2008-11-29 10:41 pm (UTC)