2014-01-18 10 views
8

Gegeben ein Array a, ich möchte countof(a), um die Anzahl der Elemente im Array als Kompilierzeitkonstante zu erhalten. Wenn ich einen Zeiger habe, möchte ich countof(p) nicht kompilieren. Dies scheint so zu sein, als ob es (1) einfach und (2) üblicherweise in SO enthalten sein sollte, aber (1) ich kann es nicht zur Arbeit bringen, und (2) Suche nach SO ergab nichts.Wie berechnen Array-Größe während der Kompilierung (ohne Zeiger zu akzeptieren)?

Hier ist mein Versuch.

#include <cstddef> 
#include <type_traits> 

template<typename T, std::size_t n, 
     typename = typename std::enable_if<std::is_array<T>::value>::type> 
constexpr std::size_t countof(T (&)[n]) { return n; } 

template<typename T, 
     typename = typename std::enable_if<std::is_pointer<T>::value>::type> 
void countof(T*) = delete; 

int main() 
{ 
    int a[10]; 
    auto asize = countof(a);    // should compile 
    static_assert(countof(a) == 10, 
       "countof(a) != 10!"); 

    int *p; 
    auto psize = countof(p);    // shouldn't compile 
} 

Hilfe?

+0

Warum sollte es kompilieren? 'typenname std :: enable_if :: value> :: type' wird falsch sein, weil' int' nicht für 'is_array' gilt. –

+0

@remyabel: Doh! Natürlich hast du recht. – KnowItAllWannabe

Antwort

7
template<typename T, std::size_t N> 
constexpr std::size_t countof(T const(&)[N]) { return N; } 

besteht beide Ihre Tests. Es gibt keine Möglichkeit, eine int* in eine T const(&)[N] umzuwandeln, so dass kein Sperrcode benötigt wird.

es zu verlängern, wir sollten hinzufügen:

template<typename T, std::size_t N> 
constexpr std::size_t countof(std::array<T,N> const&) { return N; } 

ich sogar versucht sein könnte, um sie zu erweitern size() für Behälter zu rufen. Während es normalerweise keine Kompilierzeit ist, könnte die Einheitlichkeit nützlich sein:

for(int i=0; i<countof(c); ++i) { 
    // code 
} 

oder was haben Sie.

template<typename T, std::size_t N> 
constexpr std::size_t countof(T const(&)[N]) { return N; } 

template<typename T> struct type_sink { typedef void type; }; 
template<typename T> using TypeSink = typename type_sink<T>::type; 
template<typename T, typename=void> 
struct has_size : std::false_type {}; 
template<typename T> 
struct has_size<T, TypeSink< decltype(std::declval<T>().size()) > >: 
    std::true_type 
{}; 
template<bool b, typename T=void> 
using EnableIf = typename std::enable_if<b,T>::type; 

template<typename T> 
constexpr 
EnableIf<has_size<T const&>::value,std::size_t> 
countof(T const& t) { 
    return t.size(); 
} 
// This is optional. It returns `void`, because there 
// is no need to pretend it returns `std::size_t`: 
template<typename T> 
constexpr 
EnableIf<std::is_pointer<T>::value> 
countof(T const& t) = delete; 

die ziemlich ausführlich ist, sondern gibt uns std::array Unterstützung, std::initializer_list Unterstützung, C-Stil-Array-Unterstützung - alle bei der Kompilierung - und zur Laufzeit Standardcontainer und Strings sind alle countof können. Wenn Sie einen Zeiger übergeben, wird Ihnen mitgeteilt, dass die von Ihnen aufgerufene Funktion delete ed ist.

Ich versuchte in diesem Fall eine static_assert zu erstellen, aber es kam zu Problemen mit der Auflösungsregel, dass jede template eine gültige Spezialisierung haben muss. Ich vermute, das Routing des gesamten Problems in eine countof_impl Klasse mit SFINAE-basierten Spezialisierungen könnte dieses Problem beheben.

Ein Nachteil der =delete oder static_assert Lösung ist, dass eine Überladung tatsächlich für Zeiger existiert. Wenn Sie das nicht haben, dann gibt es einfach keine gültige Funktion zum Aufrufen, die einen Zeiger braucht: das ist näher an der Wahrheit.

+0

Ich stimme zu, dass diese Lösung funktioniert, aber ich möchte wirklich die gelöschte Überladung für Zeiger verwenden, so dass Benutzer eine Fehlermeldung "Sie rufen eine gelöschte Funktion" anstelle von "eine Fehlertoleranz Vorlage-Fehler" erhalten. Das hat dazu geführt, dass ich überhaupt Überladung benutzt habe. – KnowItAllWannabe

+0

@KnowItAllWannabe: Vielleicht können Sie dann statt einer 'deleted' Funktion die Funktion tatsächlich implementieren und einen' static_assert' darin einfügen. –

+0

Der Aufruf von 'size()' für Container kann im Falle von 'initializer_list' kompiliert werden - nur seit C++ 14. –

3

So:

template <typename T, std::size_t n> 
constexpr std::size_t countof(T (&)[n]) { return n; } 

template <typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type> 
constexpr std::size_t countof(T) = delete; 
+0

'conexpr std :: size_t countof (T) = löschen;' ist besser - return countof (...) gibt einen extra Fehler, sonst –

+0

@ DieterLücking du hast Recht, behoben. –

+0

oder sogar 'constexpr std :: size_t countof (T *) = löschen;' (und so nicht 'enable_if') – Jarod42

1

Sie könnten dies tun:

#include <iostream> 
#include <type_traits> 

namespace Detail { 
    template <typename T> 
    struct array_size { 
     // A simple false is no good 
     static_assert(std::is_array<T>::value, "No Array"); 
    }; 

    template <typename T, std::size_t N> 
    struct array_size<T[N]> { 
     static constexpr std::size_t value = N; 
    }; 
} 

template <typename T> 
constexpr std::size_t array_size() { 
    return Detail::array_size<T>::value; 
} 

template <typename T> 
constexpr std::size_t array_size(const T&) { 
    return Detail::array_size<T>::value; 
} 

int main(){ 
    typedef int A[3]; 
    typedef char B[array_size<A>()]; 
    A a; 
    std::cout << array_size<A>() << array_size(a) << array_size<B>() << std::endl; 
    // int* p = a; 
    // error: static assertion failed: No Array 
    // std::cout << array_size(p) << std::endl; 
    return 0; 
} 
+0

Was sind die Vorteile dieses Ansatzes im Vergleich zu Yuri Kilochek (derzeit oben)? Wenn Sie eine static_assert für eine benutzerdefinierte Fehlermeldung verwenden möchten, können Sie dies anstelle von = delete in seine Lösung einfügen. – KnowItAllWannabe

+0

@KnowItAllWannabe Es funktioniert auch mit Typen. Das könnte jedoch auf verschiedene Arten erreicht werden. –

1

Wenn Sie alle Dimensionen glätten müssen, kann dieser Auszug in der Hand sein

//Moving to detail like 'Dieter Lücking' 
namespace detail { 

    /*recurse over ranks*/ 
    template <typename A, size_t R = std::rank<A>::value> 
    struct aux { 
     static constexpr size_t value = 
     std::extent<A, 0>::value * aux<typename std::remove_extent<A>::type>::value; 
    }; 

    /*stop condition*/ 
    template <typename A> 
    struct aux<A, 0> { 
     static constexpr size_t value = 1; 
    }; 
} 

/*convenient function, updated to use enable_if, is_array*/ 
template <typename A, typename = typename std::enable_if<std::is_array<A>::value>::type> 
constexpr size_t countof(A const &) { 
    return detail::aux<A>::value; 
} 

Anwendungsbeispiel:

int a[][3][3] = { 
    {{1,2,3}, 
    {1,2,3}, 
    {1,2,3}}, 
    {{1,2,3}, 
    {1,2,3}, 
    {1,2,3}} 
}; 
int b[countof(a)]; //size 2*3*3*1 = 18 
1

Für diejenigen von uns, die Legacy-C++ Compiler verwenden, um ohne C++ 11 des constexpr werden folgende Arbeiten:

#include <cstddef> 

template <class T, size_t N> char (*countof(T(&)[N]))[N]; // declaration only 
#define countof(x)   sizeof(*countof(x)) 

int main() 
{ 
    int a[10]; 
    size_t asize = countof(a);    // should compile 
    static_assert(countof(a) == 10, 
       "countof(a) != 10!"); 
    int *p; 
    size_t psize = countof(p);    // shouldn't compile 
} 

Diese Technik sorgt für die Kompilierung-Auswertung von countof durch die ADL Einbettung innerhalb der Operator sizeof.