how to generate code to initialize a std::vector with a custom Zero value if it exists as T::Zero?

StackOverflow https://stackoverflow.com/questions/21353810

Вопрос

BACKGROUND

I have a container class that has a std::vector<T> member that I initialize with the constructor that takes size_t n_items. I would like to initialize that vector with my own Zero() function which returns 0; by default, but if static member T::Zero exists I want to return that instead.

In my first attempt, I use expression SFINAE, but that failed due to ambiguous overload since both the generic version of Zero and the Zero have the same signature with no arguments. So now, I'm trying to convert the code into classes with operator().

I think I need to use std::enable_if somehow, but I'm not sure how to go about coding this.

FAILED ATTEMPT

#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;
}
Это было полезно?

Решение

#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;  }
};

Usage example:

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";
}

Compact version w/o trait:

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]";  }
};

Другие советы

Maybe:

#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';
}

I have a half good news: I found a very simple way (leveraging SFINAE), however the output is not exactly what I expected :)

#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;
}

As mentioned, the result is surprising was explained by dyp, we have to be careful not use std::initializer<int> by mistake and then read past the end:

3, 0, 0
Empty, Empty, Empty

Curly brace initialization yields a different result than regular brace initialization (see here) which gives the expected 0, 0, 0. So you have to use regular braces.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top