Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2003-01-11 14:40:13


----- Original Message -----
From: "Gabriel Dos Reis" <gdr_at_[hidden]>

> | However, this
> | raises another interesting problem. Many enumerations used for this
purpose
> | are unnamed:
> |
> | template<class T> struct is_ptr {
> | enum { value = false };
> | };
> |
> | template<class T> struct is_ptr<T*> {
> | enum { value = true };
> | };
> |
> | This has its own problem in the same templated "pass by reference"
> | situation. Namely, a template type argument cannot refer to an unnamed
> | type:
> |
> | template<class T> T func(const T& ref) {
> | return ref;
> | }
> |
> | int main() {
> | func( is_ptr<int*>::value );
> | // error: argument deduction yields unnamed type
>
> Probably I'm being dense here, but please could you work out *why* you
> think the above should fail?

The type of "is_ptr<int*>::value" is "is_ptr<int*>::unnamed_type" _not_
"int". Therefore template argument deduction should result in:

is_ptr<int*>::unnamed_type func(const is_ptr<int*>::unnamed_type& ref);

...which makes "T" equal to "is_ptr<int*>::unnamed_type" -- which is
illegal.

The problem is not binding the reference to const (as would be the problem
with static const without a definition), but rather the template argument
deduction attempting to bind an unnamed type to a template parameter.

Ultimately, both of these should fail:

-----------------------------------
template<class T> struct X {
    static const int value = 10;
};

// no definition of X::value

template<class T> struct Y {
    enum { value = 10 };
};

template<class T> void func(const T&) {
    // ...
    return;
}

int main() {
    func( X<int>::value ); // error: X::value has no definition
    func( Y<int>::value ); // error: cannot bind unnamed type to template
argument
    return 0;
}
-----------------------------------

The intrusive solution is like this:

func(static_cast<int>(X<int>::value));
func(static_cast<int>(Y<int>::value));

...both of which yield a temporary 'int' which is bound to the const&
parameter of 'func' with 'T' == 'int'.

The only non-intrusive solutions are to name the enumeration and define the
static constant:

-----------------------------------
template<class T> struct X {
    static const int value = 10;
};

template<class T> const int X<T>::value;

template<class T> struct Y {
    enum value_type { value = 10 };
};

template<class T> void func(const T& ref) {
    // ...
    return;
}

int main() {

    func( X<int>::value ); // okay.

        // The address of the actual object 'X<int>::value'
        // is passed to 'func' via reference.
        // 'func' is instantiated with 'T' == 'int'.

    func( X<double>::value ); // okay.

        // The address of the actual object 'X<double>::value'
        // is passed to 'func' via reference.
        // 'func' is _still_ instantiated with 'T' == 'int'
        // (i.e. the same instantiation of 'func' is used)

/* ----

problem:

Usage of the actual object causes instantiation of the definition of the
static const variable. Both 'X<int>::value' and 'X<double>::value' are
different objects, therefore space must be allocated for both (barring
optimization).

partial solution:

Map both the static const variables to the same static const definition that
has any particular value through derivation. (i.e. both 'X<int>::value' and
'X<double>::value' are both '10', therefore the same storage can be used.)
This is the primary purpose of something like 'map_integral' that was
mentioned before.

---- */

    func( Y<int>::value ); // okay.

        // A temporary is created of type 'Y<int>::value_type'.
        // The address of that temporary is bound to
        // the const& parameter of 'func'.
        // 'func' is instantiated with 'T' == 'Y<int>::value_type'.

    func( Y<double>::value ); // okay.

        // A temporary is created of type 'Y<double>::value_type'.
        // The address of that temporary is bound to
        // the const& parameter of 'func'.
        // 'func' is instantiated with 'T' == 'Y<double>::value_type',
        // which is a *different* instantiation of 'func'.
        // (i.e. unnecessary code bloat)

/* ----

problem:

Usage causes template functions to be instantiated multiple times for no
reason. IMO, this is *way* worse than the storage necessary to hold the
static constants.

solution:

There is no non-intrusive solution. It must be cast at the call site to a
common type (such as 'int' or 'bool') in order to avoid replicated template
instantiation.

---- */

    return 0;
} // end of main
-----------------------------------

These two problems only exist when a compile-time value is used like a
runtime value (i.e. static const needs a definition and enum needs a name).
The annoying part is when you have dependencies such as this:

template<class T> class is_class {
    private:
        template<class U> static char check(int U::*);
        template<class U> static char (& check(...))[2];
    public:
        ??? = sizeof(check<T>(0)) == 1;
};

The solution is annoying but better, IMHO:

template<class T, T V> struct map_integral {
    static const T value = V;
};

template<class T, T V> const T map_integral<T, V>::value;

// ...

namespace detail {

template<class T> class is_class_unsafe {
    private:
        template<class U> static char check(int U::*);
        template<class U> static char (& check(...))[2];
    public:
        enum { value = sizeof(check<T>(0)) == 1 }
};

} // detail

template<class T> struct is_class
    : map_integral<bool, detail::is_class_unsafe<T>::value> { };

This solves every problem involved that I have thought of--except for the
annoying situation when a metafunction has multiple return values (actually
values, not types). The solutions are 1) have more than one unique
map_integral like template, or 2) factor the values into other classes:

template<int I> struct size : map_integral<int, I> { };

template<class T> struct is_array
    : map_integral<bool, false>, size<-1> { };

template<class T> struct is_array<T[]>
    : map_integral<bool, true>, size<0> { };

template<class T, int I> struct is_array<T[I]>
    : map_integral<bool, true>, size<I> { };

...which yields:

is_array<T>::value
is_array<T>::size::value

Or, better yet:

template<int, class T, T V> struct unique_id
    : map_integral<T, V> { };

// ...

template<class T, int I> struct is_array<T[I]>
    : map_integral<bool, true> {
    typedef unique_id<1, int, I> size;
};

is_array<T>::size::value

Paul Mensonides


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk