lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
710 stars 26 forks source link

[C++ weekly]: Hiding implementation details with template specialization #354

Open ricardomatias opened 10 months ago

ricardomatias commented 10 months ago

Channel "C++Weekly"

Topics What is the appropriate way of hiding implementation details with template specialization?

Imagine you have the following function declaration inside a header file:

// distribution.h
template <std::size_t S>
std::array<float_t, S> createDecreasingOdds(int k);

template <std::size_t S>
std::array<float_t, S> sumDistribution(const std::array<float_t, S> &probabilities)

// Relevant for code block further down
template <std::size_t S>
std::array<float_t, S> decreasing(int k);

// usage
// auto odds = createDecreasingOdds<10>(10);
// auto distribution = sumDistribution(odds);

In the end this function will be used in another function to compose the final behavior that you actually want to expose (as a library f.ex) to the user. The problem is that the implementation can't be inside a .cpp file due std::size_t N, which would have to be implemented for every number needed (which is unknown, therefore might as well be considered infinite). If a class is considered, then that defeats the purpose as well, because you'd need different instances of the same class specialized to the size of the desired container (referring here to std::size_t N.

Example of what is wanted:

// SomeFile.cpp
#include "distribution.h"

// sumDistribution -> not accessible
// createDecreasingOdds -> not accessible

decreasing<10>(10); // accessible

The episode should be about what needs to be done in the perspective of someone making a library and how to approach this problem.

Length Bite-Sized

LB-- commented 8 months ago

You might have oversimplified your actual problem. In the example you provided, your public interface should take gsl::span or std::span and you can optionally provide convenience functions for std::array if the span conversions in user code are not to your liking. Then your private implementation can have optimized versions for specific sizes based on your needs, and fall back to generic implementations for other sizes.

//library.hpp
#include <gsl/span>

namespace library
{
    using gsl::span;

    void createDecreasingOdds(span<float_t>, int k);
    void sumDistribution(span<float_t const> probabilities, span<float_t> results);
}
//library.cpp
#include <library.hpp>

namespace library
{
    void createDecreasingOdds(span<float_t> s, int k)
    {
        switch(std::size(s))
        {
            case 0: return;
            case 1: return createDecreasingOddsOptimized<1>(s, k);
            case 2: return createDecreasingOddsOptimized<2>(s, k);
            case 3: return createDecreasingOddsOptimized<3>(s, k);
            //...
            default: break;
        }
        //generic implementation
        return;
    }
    void sumDistribution(span<float_t const> probabilities, span<float_t> results)
    {
        if(std::size(probabilities) != std::size(results))
        {
            //error handling / contract violation
            return;
        }
        switch(std::size(probabilities))
        {
            case 0: return;
            case 1: return sumDistributionOptimized<1>(probabilities, results);
            case 2: return sumDistributionOptimized<2>(probabilities, results);
            case 3: return sumDistributionOptimized<3>(probabilities, results);
            //...
            default: break;
        }
        //generic implementation
        return;
    }
}
//user code.cpp
#include <library.hpp>
#include <array>

static constexpr std::size_t const S{10};
int main()
{
    std::array<float_t, S> a;
    library::createDecreasingOdds(a, 123);
    return a.front();
}

If your actual problem is more complex than that, then I'm not sure it could be covered in a bite-sized episode, since it really depends on a lot of different factors. Public API design is a whole field of its own.

ricardomatias commented 7 months ago

Thanks for the feedback @LB--, I wasn't aware of std::span. In my case the library is for embedded development, so I can't have any dynamic allocations and structure sizes have to be defined beforehand.