Your problem is to determine at compiletime the number of members
in a template struct Target<typename T>
, where Target
is
a POD struct
template whose members are only known to consist 0 or more T
's,
and where T
for your immediate needs is double
.
You would like a solution that is consistent with T
being not even a scalar.
This can be solved, with one minor limition, by exploiting the fact:
that struct Target<T>
cannot have more members than sizeof(Target<T>)
. It is
true that a struct X
containing bitfields could have more members that
sizeof(X)
, but the type of a bitfield is {unsigned|signed} int
,
so the statement holds for struct Target<T>
, for any T
.
The minor limitation is that T
be default constructible.
Since sizeof(Target<T>)
is a compiletime upper limit for the number of fields
in Target<T>
, we can write a recursive-SFINAE solution.
Compiletime recursion is driven down from that limit by substitution failure
based upon the unsatisfiable type of an expression that attempts to
initialize a Target<T>
with too many initializers of type T: until the
substitution does not fail, and then - thanks to the POD character of Target<T>
-
we know that the number of its fields is the length of the initializer
list that it has finally accepted. We need not care with what values these
speculative initializations are attempted, provided the values are of a
type convertible to T.
(We cannot, of course, just recurse up from 0 initalizers, because Target<T>
will accept any initializer list that is not too long.)
To implement such a solution we need a compiletime means of generating initializer
lists of a type I
of arbitrary length, where I
is convertible to T
. If
I
could be an integral type, then various examples of prior art for
compiletime generation of integer sequences would spring to mind (including
examples from SO C++ luminaries:
Shaub,Wakeley,
Kühll).
The snag with this line-of-low-resistance is the constraint it apparently
entails that T
be constructible from an integral type I
. That would not
rule out a non-scalar T
, but it would very footlingly narrow the field.
The snag is only apparent, however. Since we will not care what T
s compose
our speculative initializer lists, they might as well all be same T
, and
if we stipulate only that T
be default-constructible, there will be no
difficulty in turning out identical ones. Then to build these initializer lists
we do not actually need T
to be constructible from an integral type I
.
We just need T
to be constructible from some intermediate type S
that is
constructible from that I
. And we can simply create such an S
from a
template, say, shim<U>
, for U = T
, with the desired properties that shim<U>(I)
is a constructor and shim<U> operator T() const
returns U()
.
So far so good. But is a solution of greater generality now on the cards?
We have in view a way of finding the maximum length of intializer-list-of-T
that Target<T>
will accept, and can thus infer the number of fields in the
template Target
given our preconditions on its character. Suppose we
waived those preconditions: that Target
is a template Target<T>
; that
all its fields are of type T
; that it is POD.
We would then still have in view a compiletine method of determining
whether any type Target
is capable of being constructed with any initializer-list-of-T
of length <= an arbitrary limit M
. This is likely to be more useful than
the preliminary idea (though still recheche enough).
A trivial cost of this extra generality would be that the interface of the
template solution can no longer be parameterized simply by Target
and T
whenever Target
is a template on T
, per your problem. In that case it will
have to be parameterized by Target<T>
and T
. A more important penalty
would be the fact that we would now need additionally to parameterize the
template interface with that M
= the limiting length within which a
initializer list for Target
shall be sought. Why? Because if Target
is not
POD, then sizeof(Target)
is no longer an upper bound on the number
of initializers that Target
might accept.
The need for such an M
was just what you didn't like about your own solution.
But the more general solution can still avoid the need for it whenever
Target
is POD. Since that property is detectable by
std::is_pod<Target>::value == true
, the more general solution can default M
in that case to sizeof(Target)
and otherwise not default it at all.
The following solution is the residue of all this. For my compiletime-integer-sequences
apparatus I have chosen to plagiarize C++ standards committee member
Daniel Krügler,
make_indices.h
#ifndef MAKE_INDICES_H
#define MAKE_INDICES_H
/* After [Daniel Krügler]
https://groups.google.com/forum/?fromgroups#!topic/comp.lang.c++.moderated/H6icuxL0NAY
*/
template<unsigned...> struct indices {};
namespace detail {
template<unsigned I, class Indices, unsigned N>
struct make_indices;
template<unsigned I, unsigned... Indices, unsigned N>
struct make_indices<I, indices<Indices...>, N>
{
typedef typename make_indices<I + 1, indices<Indices..., I>, N>::type type;
};
template<unsigned N, unsigned... Indices>
struct make_indices<N, indices<Indices...>, N>
{
typedef indices<Indices...> type;
};
} // namespace detail
template<unsigned N>
struct make_indices : detail::make_indices<0, indices<>, N> {};
#endif // EOF
Then my contribition: initializer_count.h
#ifndef INITIALIZER_COUNT_H
#define INITIALIZER_COUNT_H
#include "make_indices.h"
#include <type_traits>
namespace detail {
/* class detail::shim<U> is a convenience wrapper of U whose
sole purpose is to be constructible from unsigned and
convertible to a U.
*/
template<typename U>
struct shim
{
static_assert(std::is_default_constructible<U>::value,
"U must be default-constructible for detail::shim<U>");
explicit shim(unsigned){};
operator U () const {
return U();
}
};
} // namespace detail
/*
class initializer_count<Target,T> will export
`static const unsigned value` == the maximum length <= Size of
initializer list of T that Target will accept.
Size defaults to sizeof(Target) if Target id POD. Otherwise a static_assert
is tripped if Size is defaulted.
*/
template<
class Target,
typename T,
unsigned Size = std::is_pod<Target>::value ? sizeof(Target) : unsigned(-1)
>
struct initializer_count;
// Terminal case
template<class Target, typename T>
struct initializer_count<Target,T,0>
{
static const unsigned value = 0;
};
// Recursion case.
template<class Target, typename T, unsigned Size>
struct initializer_count
{
static_assert(Size != unsigned(-1),
"Size cannot be defaulted for non-POD "
"Target in initializer_count<Target,T,Size>");
// SFINAE success. Target can be initialized with a list of length Size
template<unsigned ...I>
static constexpr auto count(indices<I...>) ->
decltype(Target{detail::shim<T>(I)...},Size) {
return Size;
}
// SFINAE failure.
template<unsigned ...I>
static constexpr unsigned count(...) {
// Recurse to Size - 1
return initializer_count<Target,T,Size - 1>::value;
}
static const unsigned value = count(typename make_indices<Size>::type());
};
#endif // EOF
A test program (gcc 4.7.2/4.8.1, clang 3.2):
#include "initializer_count.h"
struct non_pod
{
non_pod(){}
non_pod(double a, short b)
: _a(a),_b(b){}
double _a = 42.0;
short _b = 42;
};
template <typename T>
struct five_unknowns
{
T a;
T b;
T c;
T d;
T e;
};
template <typename T>
struct one_unknown
{
T a;
};
template <typename T>
struct zero_unknowns {};
#include <iostream>
using namespace std;
int main()
{
static const unsigned initializer_max = 100;
static_assert(!std::is_pod<non_pod>::value,"");
cout << initializer_count<zero_unknowns<char>,char>::value << endl;
cout << initializer_count<one_unknown<int>,int>::value << endl;
cout << initializer_count<five_unknowns<double>,double>::value << endl;
// Need initializer_max for rest non-pod targets...
cout <<
initializer_count<five_unknowns<non_pod>,non_pod,initializer_max>::value
<< endl;
cout << initializer_count<non_pod,short,initializer_max>::value << endl;
return 0;
}
// EOF
Expected output:
0
1
5
5
2