8

I am trying to make a simple LookUpTable based on an array of integers, where the idea is to have it calculated at compile time.

Trying to make it possible to use it for any other future tables of various integer types I might have, I need it as a template.

So I have a LookUpTable.h

#ifndef LOOKUPTABLE_H
#define LOOKUPTABLE_H

#include <stdexcept> // out_of_range

template <typename T, std::size_t NUMBER_OF_ELEMENTS>
class LookUpTableIndexed
{
    private:
        //constexpr static std::size_t NUMBER_OF_ELEMENTS = N;

        // LookUpTable
        T m_lut[ NUMBER_OF_ELEMENTS ] {}; // ESSENTIAL T Default Constructor for COMPILE-TIME INTERPRETER!

    public:
        // Construct and Populate the LookUpTable such that;
        //   INDICES of values are MAPPED to the DATA values stored
        constexpr LookUpTableIndexed() : m_lut {}
        {
            //ctor
        }

        // Returns the number of values stored
        constexpr std::size_t size() const {return NUMBER_OF_ELEMENTS;}

        // Returns the DATA value at the given INDEX
        constexpr T& operator[](std::size_t n)
        {
            if (n < NUMBER_OF_ELEMENTS)
                return m_lut[n];
            else throw std::out_of_range("LookUpTableIndexed[] : OutOfRange!");
        }
        constexpr const T& operator[](std::size_t n) const
        {
            if (n < NUMBER_OF_ELEMENTS)
                return m_lut[n];
            else throw std::out_of_range("LookUpTableIndexed[] const : OutOfRange!");
        }

        using iterator = T*;

        // Returns beginning and end of LookUpTable
        constexpr iterator begin() {return &m_lut[0                 ];}
        constexpr iterator end  () {return &m_lut[NUMBER_OF_ELEMENTS];}
};

#endif // LOOKUPTABLE_H

And I'm trying to use it in a class for rapid attenuation of an integer signal wrt an integer distance.

eg. This is just a sample usage as Foo.h

#ifndef FOO_H
#define FOO_H

#include <limits>   // max, digits
#include <stdlib.h> // abs

#include "LookUpTable.h" // LookUpTableIndexed

class Foo
{
private:
    template <typename    TDistance,
              TDistance   MAXIMUM_DISTANCE,
              std::size_t NUMBER_OF_DIGITS>
    struct DistanceAttenuation
    {
    private:
        // Maximum value that can be held in this type
        //constexpr auto MAXIMUM_DISTANCE = std::numeric_limits<TDistance>::max();

        // Number of bits used by this type
        //constexpr auto NUMBER_OF_DIGITS = std::numeric_limits<TDistance>::digits;

        // LookUpTable
        LookUpTableIndexed<TDistance, NUMBER_OF_DIGITS> m_attenuationRangeUpperLimit {}; // ESSENTIAL LookUpTable Default Constructor for COMPILE-TIME INTERPRETER!

        // Returns the number of bits to BIT-SHIFT-RIGHT, attenuate, some signal
        // given its distance from source
        constexpr std::size_t attenuateBy(const TDistance distance)
        {
            for (std::size_t i {NUMBER_OF_DIGITS}; (i > 0); --i)
            {
                // While distance exceeds upper-limit, keep trying values
                if (distance >= m_attenuationRangeUpperLimit[i - 1])
                {
                    // Found RANGE the given distance occupies
                    return (i - 1);
                }
            }
            throw std::logic_error("DistanceAttenuation::attenuateBy(Cannot attenuate signal using given distance!)");
        }

    public:
        // Calculate the distance correction factors for signals
        // so they can be attenuated to emulate the the effects of distance on signal strength
        // ...USING THE INVERSE SQUARE RELATIONSHIP OF DISTANCE TO SIGNAL STRENGTH
        constexpr DistanceAttenuation() : m_attenuationRangeUpperLimit {}
        {
            //ctor

            // Populate the LookUpTable
            for (std::size_t i {0}; (i < NUMBER_OF_DIGITS); ++i)
            {
                TDistance goo = 0; // Not an attenuation calculation
                TDistance hoo = 0; // **FOR TEST ONLY!**
                m_attenuationRangeUpperLimit[i] = MAXIMUM_DISTANCE - goo - hoo;
            }
            static_assert((m_attenuationRangeUpperLimit[0] == MAXIMUM_DISTANCE),
                          "DistanceAttenuation : Failed to Build LUT!");
       }

        // Attenuate the signal, s, by the effect of the distance 
        // by some factor, a, where;
        // Positive contribution values are attenuated DOWN toward ZERO
        // Negative                                    UP          ZERO
        constexpr signed int attenuateSignal(const signed int s, const int a)
        {
            return (s < 0)? -(abs(s) >> a) :
                             (abs(s) >> a);
        }
        constexpr signed int attenuateSignalByDistance(const signed int s, const TDistance d)
        {
            return attenuateSignal(s, attenuateBy(d));
        }

    };

    using SDistance_t = unsigned int;

    constexpr static auto m_distanceAttenuation = DistanceAttenuation<SDistance_t,
                                                                      std::numeric_limits<SDistance_t>::max(),
                                                                      std::numeric_limits<SDistance_t>::digits>();

public:
    Foo() {}
    ~Foo() {}

    // Do some integer foo
    signed int attenuateFoo(signed int signal, SDistance_t distance) {return m_distanceAttenuation::attenuateSignalByDistance(signal, distance);}

};

#endif // FOO_H

I have tried to do this several ways, using the youtube video tutorial by CppCon 2015: Scott Schurr “constexpr: Applications" and others, but it won't compile giving the error;

error: 'constexpr static auto m_distanceAttenuation...' used before its definition

and the static asserts fail with

error: non-constant condition for static assertion

indicating it isn't calculating anything at compile-time.

I'm new to C++.

I know I'm doing something obvious but I don't know what it is.

Am I misusing static or constexpr?

numeric_limits are constexpr?

What am I doing wrong? Thank you.

David H Parry
  • 299
  • 3
  • 13
  • 1
    For one you are missing the closing `>` for `= DistanceAttenuation<...`. Is that a copy error? Aside from this, I very much doubt, you can do what you are trying to do in the constructor of `DistanceAttenuation` but I haven't used c++14's constexpr much. – MikeMB Sep 07 '16 at 11:52
  • Did you try doing what the very clear compiler error message suggests you should do? Do you understand what "used before definition" means? – Sam Varshavchik Sep 07 '16 at 12:08
  • 1
    Don't know enough for proper answer, but: 1) "used before its definition" seems to be caused by using nested classes. If you move DistanceAttenuation out of Foo, it will work. 2) The constexpr constructor must be compilable to be run in runtime if needed, in such case the expression inside `static_assert` would not be constexpr. – michalsrb Sep 07 '16 at 12:14
  • Thank you... but *why* doesn't it work as a nested-class? – David H Parry Sep 07 '16 at 13:24
  • @SamVarshavchik In this instance, no. – David H Parry Sep 07 '16 at 16:43

1 Answers1

0

Some observations

1) as observed by michalsrb, Foo isn't complete when you initialize m_distanceAttenuation and DistanceAttenuation is part of Foo, so is incomplete.

Unfortunately you can't initialize a static constexpr member with an incomplete type (as better explained by jogojapan in this answer).

Suggestion: define DistanceAttenuation it outside (and before) Foo; so it's a complete type and can be used to initialize m_distanceAttenuation; something like

 template <typename    TDistance,
           TDistance   MAXIMUM_DISTANCE,
           std::size_t NUMBER_OF_DIGITS>
 struct DistanceAttenuation
 {
   // ...
 };

class Foo
{
  // ...
};

2) in C++14, a constexpr method isn't a const method; suggestion: define the following method as const too or you can't use they in some constexpr expressions

constexpr std::size_t attenuateBy (const TDistance distance) const
constexpr signed int attenuateSignal(const signed int s, const int a) const
constexpr signed int attenuateSignalByDistance(const signed int s, const TDistance d) const

3) in attenuateBy(), the test in the following for is ever true

for (std::size_t i {NUMBER_OF_DIGITS - 1}; (i >= 0); --i)

because a std::size_t is ever >= 0, so the for goes in loop and never exit; suggestion: redefine i as int or long

4) in attenuateFoo() you use m_DistanceAttenuation where the variable is defined as m_distanceAttenuation; suggestion: correct che name of the variable used

5) in attenuateFoo() you call the method attenuateSignalByDistance() using the :: operator; suggestion: use the . operator, so (considering point (4) too)

signed int attenuateFoo(signed int signal, SDistance_t distance)
 {return m_distanceAttenuation.attenuateSignalByDistance(signal, distance);}
Community
  • 1
  • 1
max66
  • 65,235
  • 10
  • 71
  • 111
  • I'm not sure what this means **1) as observed by michalsrb, DistanceAttenuation isn't defined in Foo if you define it inside Foo**... – David H Parry Sep 07 '16 at 13:26
  • I didn't realize *constexpr* didn't work as an implicit kind of *const*. I thought it was simply a kind of *const++* – David H Parry Sep 07 '16 at 13:28
  • @DavidHParry - You're not sure about what I mean with "DistanceAttenuation isn't defined in Foo if you define it inside Foo [...]" because I've written a stupid thing; sorry. Corrected answer (I hope). – max66 Sep 07 '16 at 21:06
  • @DavidHParry - about constness, take in count that in C++11 a method that is `constexpr` is `const` too; in C++14 this is changed and `constexpr` doesn't imply `const` (for methods). – max66 Sep 07 '16 at 21:08
  • That does make more sense. I think it means that classes are effectively *descriptions* and without an *instance* they have no memory allocated to them where a compile-time block could reside. A nested class would simply be a description within a description and so have nowhere for a compile-time block of data to reside... until instantiated or statically defined? I think that's almost right... I think. – David H Parry Sep 07 '16 at 21:20
  • Also I did learn about *constexpr* when learning C++11... I wasn't aware it had changed that much in C++14. Losing its *const* 'ness. I knew the limitations on recursion and constructors had changed. Must have missed that. Thanks. – David H Parry Sep 07 '16 at 21:23
  • @DavidHParry : In C++14 literal types can be mutated and still be constexpr, so it's necessary to have constexpr but non-const member functions. – ildjarn Sep 13 '16 at 04:14