N3405=12-0095
2012-09-22
Mike Spertus,
Symantec
mike_spertus@symantec.com
Subsumes N2332=07-0192
This paper collects several tweaks to C++' template mechanism.
The motivating example is a putative reflection type trait giving properties of a class member.
struct A {
void f(int i);
double g(size_t s);
};
/* ... */
cout << describe<&A::f>::name; // Prints "f"
cout << describe<&A::g>::arity; // prints 1
The question is "what should the declaration of describe look like?" Since it takes
a non-type template parameter, we need to specify the type of the parameter
using the familiar (100k hits on Google) “template<class T, T t>” idiom
template<typename T, T t> struct describe;
Of course, we would then need to
change are original code to call describe with the uglier
cout << describe<decltype(&A::f), &A::f>::name; // Prints "f"
cout << describe<decltype(&A::g), &A::g>::arity; // prints 1
Significantly uglier examples can be constructed with variadics.
template<typename T t> struct describe;
/* ... */
cout << describe<&A::f>::name; // OK. T is void(A::*)(int)
cout << describe<&A::g>::arity; // OK. T is double(A::*)(size_t)
Handling variadics works as follows:
// Takes arbitrary list of non-type parameters
template<typename... T... t> struct A {/* ... */};
A<7, "foo"> a; // T is the parameter pack <int, const char[4]>
Note that this proposal works for functions as well.
template<typename T t>
T f();
int i = f<7>() // OK, T is int
As a motivating example, I've found code like the following to be all too familiar in projects I have
been involved in.
log << "Getting ready to call Order::execute on " << *this << with arguments "
<< a1 << ", " << a2 << endl;
auto result = execute(a1, a2);
log << "Order::execute returned " << result << endl;
// Convenience function to variadically print to a stream
template<typename T, typename... Ts>
void printArgs(ostream &os, T&& t, Ts&&... ts) {
os << t << ", ";
printArgs(os, forward<Ts>(ts)...);
};
void printArgs(ostream &os) {}
// Write the wrapper once and for all
template<typename Result(class T::*m)(typename... Args)>
Result wrap_and_log ()(T *t, Args&&... args) {
log << "Getting ready to call " << describe<m>::name << " with arguments ";
printArgs(log, args...);
Result result = t->*m(forward<Args>(args)...);
log << describe<m>::name << " returned " << result;
return result;
}
/* ... */
// Now that's out of the way, we can replace the above example by (less than) one line
auto result = wrap_and_log(&A::f, this, a1, a2);
This is a revival of N2332=07-0192, which was deferred to the current standards cycle.
According to the standard, template argument deduction may be done for functions. However, many important C++ idioms, such as for_each loops and factory patterns, use class constructors as if they were functions. Unfortunately, template argument deduction is not done in that case. This makes such constructs substantially more difficult than they need to be, and also makes functors less function-like than they need to be. Furthermore, it is weirdly inconsistent for constructors to participate in overload resolution but not in template argument deduction.
This is often handled by the awkward make_* idiom for constructing template classes. For example, whereas new X(/* ... */) suffices to create non-template classes, I often need to say something like make_tuple(7, bind(std::multiplies<int>, _1, _1)). Does this extra delegation to the make_tuple function serve any necessary purpose? Well, I can always create a (simple) tuple like new tuple<int, double>(5, 3.2), but the template arguments are redundant because they are implied by the arguments (this is why make_tuple exists!). Really, it would be much better if you could just say new tuple(5, 3.2), which is what we are proposing.
Another problem with the make_* idiom is that it can't be used in all of the ways constructors are used, so
code like the following is awkward.
using namespace std;
template<class Func>
class Foo() {
public:
Foo(Func f) : func(f) {}
void operator()(int i) {
os << "Calling with " << i << endl;
f(i);
}
private:
Func func;
};
void bar(vector<int> vi) {
int a;
for_each(vi.begin(), vi.end(), Foo<???>([&](int i) { a += i*i; }));
}
If we allowed the compiler to deduce the template arguments, we could simple write the for_each loop
asfor_each(vi.begin(), vi.end(), Foo([&](int i) { a += i*i; }
What are the limits on this deduction? All of the classes
non-defaulted template parameters need to be directly included in the function arguments. This is not so bad, because template
parameter deduction for ordinary functions has the exact same limitation, so we
are nothing if not consistent. Neither
template<class T>
struct F {
F(typename T::x);
};
auto a = F(x);// Error: Cannot deduce T
nor
template<class T>
T F(typename T::x);
auto a = F(x);// Error: Cannot deduce T
compile.
template<typename... T, void (*)(T...), T... t>
struct S { /* ... */};
void f(int, double);
S<int, double, f, 5, 4.1> s;
Currently, this doesn't work because all template parameters
are absorbed by T, even though they are not types.
This proposal is simply that a non-type passed as a template parameter
terminates the scope of a typename.... Note that
examples of this sort come up when trying to pass a function
as a template parameter (Although the T for two tidbit
helps here also, neither moots the other). Likewise, it is
not unreasonable to desire passing some types as parameters
and then passing non-type template parameters of the given types.
However, we cannot fulfill these needs. First, the typedef for WidgetFactory above is not possible because templates cannot have a variable number of parameters. Second, the template syntax Create<Xxx<() is not legal because virtual functions cannot be templates.He then uses some very ugly (but necessary) techniques to simulate virtual fully-specialized template functions to create the factory with code that (leveraging variadics) is more or less equivalent to the following (see Chapter 9 for explanation of these techniques)
template<typename T> struct Type2Type {};
template<typename... Ts> struct AbstractFactoryHelper;
template<> struct AbstractFactoryHelper<> {};
template<typename T, typename... Ts>
struct AbstractFactoryHelper<T, Ts...> : public AbstractFactoryHelper<Ts...> {
virtual T *doCreate(Type2Type<T>) = 0;
};
template<typename... Ts>
struct AbstractFactory : public AbstractFactoryHelper<Ts...> {
template<typename T>
T *create() {
return doCreate(Type2Type<T>());
}
}
If we allowed fully-specialized methods to be virtual, not only is the code
shorter, it is clearer and more natural with fewer guru techniques (we do not want to pursue
conciseness for its own sake!).
template<typename... Ts>struct AbstractFactoryHelper;
template<> struct AbstractFactoryHelper<> {};
template<typename T, typename... Ts>
struct AbstractFactoryHelper<T, Ts...> : public AbstractFactoryHelper<Ts...> {
template<> virtual T *create<T>() = 0;
};
template<typename... Ts>
struct AbstractFactory : public AbstractFactoryHelper<Ts...> {
template<typename T> T *create();
}
One requirement is likely to be that the specializations are visible in all
translation units, but that is satisfied in the motivating examples
and can be viewed as a (possibly extended) ODR requirement. The warning
in §14.7.3p8 of the standard saying
When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.still applies, but we've already accepted the consequences of having a language with specializations, let's maximize their benefit.