2014-01-25 1 views
2

ПРЕДПОСЫЛКИкак сгенерировать код для инициализации std :: vector с пользовательским значением Zero, если он существует как T :: Zero?

У меня есть класс контейнера, который имеет std::vector<T> элемент, который я инициализировать с конструктором, который принимает size_t n_items. Я хотел бы инициализировать этот вектор с моей собственной функцией Zero(), которая по умолчанию returns 0;, но если существует статический член T::Zero, я хочу вернуть это вместо этого.

В моей первой попытке я использую выражение SFINAE, но это не удалось из-за неоднозначной перегрузки, поскольку и общая версия Zero и Zero имеет одну и ту же подпись без аргументов. Итак, теперь я пытаюсь преобразовать код в классы с operator().

Как мне кажется, мне нужно использовать std::enable_if, но я не уверен, как это сделать.

неудачная попытка

#include <cassert> 
#include <iostream> 
#include <vector> 

template<typename T> 
struct Zero 
{ 
     T operator() const { return 0; } 
}; 

template<typename T> 
struct Zero 
{ 
     auto operator() const -> 
       decltype(T::Zero) 
     { 
       return T::Zero; 
     } 
}; 

struct Foo 
{ 
     char m_c; 
     static Foo Zero; 

     Foo() : m_c('a') { } 
     Foo(char c) : m_c(c) { } 
     bool operator==(Foo const& rhs) const { return m_c==rhs.m_c; } 
     friend std::ostream& operator<<(std::ostream& os, Foo const& rhs) 
     { 
       os << (char)(rhs.m_c); 
       return os; 
     } 
}; 

Foo Foo::Zero('z'); 

int 
main(int argc, char** argv) 
{ 
     std::vector<unsigned> v(5, Zero<unsigned>()); 
     std::vector<Foo>  w(3, Zero<Foo>()  ); 

     for(auto& x : v) 
       std::cout << x << "\n"; 
     std::cout << "---------------------------------\n"; 
     for(auto& x : w) 
     { 
       assert(x==Foo::Zero); 
       std::cout << x << "\n"; 
     } 

     std::cout << "ZERO = " << Foo::Zero << "\n"; 

     return 0; 
} 

ответ

5
#include <string> 
#include <iostream> 
#include <vector> 
#include <type_traits> 

namespace detail 
{ 
    template<class T, class = decltype(T::zero)> 
    std::true_type has_zero_impl(int); 

    template<class T> 
    std::false_type has_zero_impl(short); 
} 

template<class T> 
using has_zero = decltype(detail::has_zero_impl<T>(0)); 

template<class T, class = has_zero<T>> 
struct zero 
{ 
    constexpr static T get() { return T(); } 
}; 

template<class T> 
struct zero<T, std::true_type> 
{ 
    constexpr static auto get() -> decltype(T::zero) 
    { return T::zero; } 
}; 

Пример использования:

template<> 
struct zero<std::string> 
{ 
    static constexpr const char* get() 
    { return "[Empty]"; } 
}; 

struct foo 
{ 
    int m; 
    static constexpr int zero = 42; 
}; 

int main() 
{ 
    std::cout << zero<int>::get() << "\n"; 
    std::cout << zero<std::string>::get() << "\n"; 
    std::cout << zero<foo>::get() << "\n"; 
} 

Компактная версия без признака:

template<class T> 
struct zero 
{ 
private: 
    template<class X> 
    constexpr static decltype(X::zero) zero_impl(int) 
    { return X::zero; } 

    template<class X> 
    constexpr static X zero_impl(short) 
    { return X(); } 

public: 
    constexpr static auto get() 
    -> decltype(zero_impl<T>(0)) 
    { 
     return zero_impl<T>(0); 
    } 
}; 

template<> 
struct zero<std::string> 
{ 
    constexpr static const char* get() 
    { return "[Empty]"; } 
}; 
+0

То, что 'zero' также может быть статической функцией-членом вместо статического элемента данных. – dyp

+0

Также возможно использовать пользовательский распределитель для 'std :: vector', инициализация которого использует настраиваемый' zero'. – dyp

1

Может быть:

#include <iostream> 
#include <vector> 

template <typename T> 
inline T zero() { 
    return T(); 
} 

template <> 
inline std::string zero<std::string>() { 
    return "[Empty]"; 
} 

int main() { 
    std::vector<std::string> v(1, zero<std::string>()); 
    std::cout << v[0] << '\n'; 
} 
+0

ok - это отвечает первой половине моей проблемы - есть ли способ автоматически генерировать специализацию, если Foo :: Zero существует и возвращает его (используя возможности C++ 11)? – kfmfe04

+0

@ kfmfe04, вы можете представить себе, что вторая половина - это утка, набирая –

1

У меня есть половина хорошие новости: Я нашел очень простой способ (с использованием SFINAE), однако не совсем то, что я ожидал :)

#include <iostream> 
#include <string> 
#include <vector> 

// Meat 
template <typename T, typename = decltype(T::Zero)> 
auto zero_init_impl(int) -> T { return T::Zero; } 

template <typename T> 
auto zero_init_impl(...) -> T { return T{}; } 

template <typename T> 
auto zero_init() -> T { return zero_init_impl<T>(0); } 

// Example 
struct Special { static Special const Zero; std::string data; }; 

Special const Special::Zero = { "Empty" }; 

int main() { 
    std::vector<int> const v{3, zero_init<int>()}; 
    std::vector<Special> const v2{3, zero_init<Special>()}; 

    std::cout << v[0] << ", " << v[1] << ", " << v[2] << "\n"; 
    std::cout << v2[0].data << ", " << v2[1].data << ", " << v2[2].data << "\n"; 
    return 0; 
} 

Как уже упоминалось, the result is surprising объяснялось DYP, мы должны быть осторожны, чтобы не использовать std::initializer<int> по ошибке, а затем прочитать в конце прошлого:

3, 0, 0 
Empty, Empty, Empty 

Инициализация фигурной скобки дает другой результат, чем обычная инициализация скобки (see here), которая дает ожидаемый 0, 0, 0. Таким образом, вы должны использовать регулярные фигурные скобки.

+0

'zero_init ()' дает 'int', поэтому вы имеете' std :: vector const v {3, 0}; 'который вызывает конструктор' std :: initializer_list'. Ура! для равномерной инициализации. – dyp

+0

@ dyp: Конечно, глупо меня! И поскольку 'v [2]' не отмечен, он читает мимо конца (печать размера указывает 2); спасибо, что поймал это, я предполагал, что это как-то связано с этим, но почему-то не поняла, что вектор короче, чем я ожидал. –