Position aggregate fun

Discussion about development of draughts in the time of computer and Internet.
Post Reply
Rein Halbersma
Posts: 1722
Joined: Wed Apr 14, 2004 16:04
Contact:

Position aggregate fun

Post by Rein Halbersma » Tue Mar 19, 2013 21:23

Here's a fun piece of C++11 code that allows a very flexible definition of a Position aggregate with a variable number of data members, without having to rewrite tedious get()/set() functions whenever you change the class definition, and while still calling make(Move) on all the basic building blocks!
Spoiler:

Code: Select all

#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <tuple>

// bitboard representation of piece-sets
struct BB
{
    uint64_t data_;
    
    explicit BB(uint64_t d): data_(d) {}
    
    template<typename Move>
    void make(Move const& /* m */)
    {
       std::cout << "updating BB" << "\n";
    }
};

// side to move
struct Color
{
    bool data_;
    
    explicit Color(bool d): data_(d) {}
    
    template<typename Move>
    void make(Move const& /* m */)
    {
       std::cout << "updating Color" << "\n";
    }
};

namespace detail {
   
template<typename Move, std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
do_make(Move&& /*m*/, std::tuple<Tp...>& /*t*/)
{ 
    // no-op for empty tuples   
}

template<typename Move, std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
do_make(Move&& m, std::tuple<Tp...>& t)
{
    std::get<I>(t).make(std::forward<Move>(m));
    
    // recurse to next data member
    do_make<Move, I + 1, Tp...>(std::forward<Move>(m), t);
}

} // namespace detail

template<typename... Types>
class Aggregate
{
private:
    // the data
    std::tuple<Types...> data_;

public:
    // forward all constructor arguments to the std::tuple constructor 
    template<typename... Args>
    Aggregate(Args&&... args)
    :
        data_(std::forward<Args>(args)...) 
    {}

    // aggregation read-semantics: member-by-member
    template<std::size_t I>
    auto get() const -> decltype(std::get<I>(data_)) 
    { 
        return std::get<I>(data_); 
    }
    
    // transactional write-semantics: all members at once
    template<typename Move>
    void make(Move&& m) 
    {
        detail::do_make(std::forward<Move>(m), data_);
    }
};

// define accessor names
enum: int { black_pieces, white_pieces, kings, color };
enum: bool { black, white };
typedef int Move;

// define Position type
typedef Aggregate<BB, BB, BB, Color> Position;

int main()
{
   Position p { BB{0xF0}, BB{0x0F}, BB{0x0}, Color{white} };
   std::cout << std::hex;
   
   // can read every member separately
   std::cout << std::setw(3) << p.get<black_pieces>().data_ << "\n";
   std::cout << std::setw(3) << p.get<white_pieces>().data_ << "\n";
   std::cout << std::setw(3) << p.get<kings>().data_ << "\n";
   std::cout << p.get<color>().data_ << "\n";
   
   // can only update all members at once
   Move move{0};
   p.make(move);
}
It works on gcc 4.7.2, Clang 3.2, Intel 13.0. The trick uses variadic templates, initializer lists, type deduction and move semantics.

The main idea is that a Position is considered a tuple aggregate with respect to reads (e.g. during eval/move generation), and a single transactional entity with respect to writes (i.e. when making moves). If every tuple member has a make(Move) member function, then the Aggregate class template will automatically call all of them (using variadic template recursion). Users only have to define which basic building blocks are present in the Position, and the get() member template will automatically extract each block when indexed with the appropriate number (which the enum has named for readability). Also the Position constructor template will forward all arguments to the constructors of the building blocks.

Post Reply