2

In an effort to make life a bit easier when solving a complicated PDE system, I am writing a C++ wrapper around (the relevant portion of) a C numerical library. When dealing with multiple unknowns, the library just assigns an array to each grid point and pass its pointer to your user-specified function. The user could refer to each unknown via F[0], F[1], ...

Of course, these unknowns usually have proper mathematical names, and it would be nice to be able to refer to them as such. An obvious solution is to define a struct like

template <typename T>
struct unknowns
{
    T a;
    T b;
    T c;
    T d;
};

and convert the double* to unknowns<double>* using reinterpret_cast. This does seem to work, however after reading Can I treat a struct like an array? I have been trying to find a better solution that guarantees correctness in this conversion process (and also gracefully dealing with non-scalar T -- something that I might need a bit later).

The second most obvious solution, then, is to redefine vars so that it holds T& instead, and then concoct up something like the following

template <size_t DOF>
class factory
{
private:

    template <template <typename> class Target, typename T, typename... Index>
    static typename std::enable_if<(sizeof...(Index) < DOF), Target<T>>::type
    _do_construct(T* array, Index... index)
    {
        return _do_construct<Target>(array, index..., sizeof...(Index));
    }   

    template <template <typename> class Target, typename T, typename... Index>
    static typename std::enable_if<sizeof...(Index) == DOF, Target<T>>::type
    _do_construct(T* array, Index... index)
    {
        return { array[index]... };
    }

public:

    template <template <typename> class Target, typename T>
    static Target<T> wrap_array(T* array)
    {
        return _do_construct<Target>(array);
    }
};

With this, I can now convert a library-provided double* f to an unknowns<double> F safely via

    auto F = factory<4>::wrap_array<unknowns>(f);

which is rather nice.

What would be even better now is if I could also omit the <4>, however I couldn't figure out how to do this. Presumably it should be possible to use SFINAE to determine how many members the struct holds, but std::is_constructible doesn't seem to be able to do what I need.

Could anyone suggest how this might be possible?

Community
  • 1
  • 1
Saran Tunyasuvunakool
  • 1,064
  • 1
  • 9
  • 23
  • side-note: within a year time we're supposed to get concepts lite to solve this problem – Balog Pal Jun 06 '13 at 15:23
  • 1
    How do you want to omit the "4"? Who else on that line knows the value "4"? – Kerrek SB Jun 06 '13 at 15:24
  • @Kerrek SB In this case, since `unknowns` have four members, it can only constructed from an initializer list with precisely 4 members. Anything else would fail. Surely there is a way to exploit this and have the compiler deduce the `<4>` automatically? – Saran Tunyasuvunakool Jun 06 '13 at 15:27
  • Oh, now I see what you mean. Interesting... – Kerrek SB Jun 06 '13 at 15:32
  • I have encountered the same problem. Back then, I defined an enum, holding the mathematical names. For example `enum { X, Y, Z, SIZE};` and then given an array `v` I would type `v[Y]` instead of `v[1]`, etc. I am curious what answers you get, up voted your question! – Ali Jun 07 '13 at 08:39

2 Answers2

1

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 Ts 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
Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • I ended up writing something very similar to this myself but the compiler wouldn't take it. I haven't quite figured out exactly what is the difference between your solution and mine, but I think it might come down to the relative lack of C++11 support in Intel. Will report back when I figure out more. Thanks a lot! – Saran Tunyasuvunakool Jun 23 '13 at 15:09
0

How about another wrapper:

template <typename T, unsigned int N>
Target<T> wrap_actual_array(T const (&a)[N])
{
    return factory<N>::wrap_array<unkowns>(a);
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084