Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Blotto Skorzany
Nov 7, 2008

He's a PSoC, loose and runnin'
came the whisper from each lip
And he's here to do some business with
the bad ADC on his chip
bad ADC on his chiiiiip
Having had to write a bunch of fixed point cal/comp math for instrumentation firmware, I would recommend avoiding it if possible. Equivalent code with floats is way easier to read and harder to gently caress up in a painful way, and you have less analysis to do to ensure overflow doesn't occur and you have sufficient granularity. If one single fmul on Cortex M0+ hadn't blown through the majority of my cycle budget I would have used floats and never looked back.

Semi-related Fun Fact: M0/M0+ doesn't even have an integer divide instruction!

Adbot
ADBOT LOVES YOU

Foxfire_
Nov 8, 2010

If you don't need lots of precision through intermediate calculations but also don't have hardware float32, I find it easier to think about things as an integer value in a changed unit. i.e. instead of having a fixed point value in degrees with an implicit binary point after some bit, have it be an integer value in hundreths of a degree. Doing it in base-10 makes it easy to think about roundoff behavior and to read/write literal values.

Dominoes
Sep 20, 2007

An observation: If messing with things like this, it sounds wise to #1 separate/abstract, so your program code acts like it's manipulating normal numbers, and #2: use third party libs where the work's done if able. This feels like a trap where optimization and program logic collide to cause tough-to-read/write code, and bugs.

Btw, this STM32Cube IDE is great. It exposures most (all?) chip settings, pin functions, dev board extras etc in a UI, which generates config code, so you know what it's capable of, what the defaults are etc, in a way more directly linked to the code than reading a datasheet. It's even helping as a reference in a non-Cube HAL project using the same chip/board. Ie all I have for that is API docs, without much hint to default values. SPI polarity? Phase? What are those? Ref the IDE and see what they're set to.

Dominoes fucked around with this message at 05:24 on May 22, 2020

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Foxfire_ posted:

If you don't need lots of precision through intermediate calculations but also don't have hardware float32, I find it easier to think about things as an integer value in a changed unit. i.e. instead of having a fixed point value in degrees with an implicit binary point after some bit, have it be an integer value in hundreths of a degree. Doing it in base-10 makes it easy to think about roundoff behavior and to read/write literal values.

? That's what fixed point is. You still have to scale after a multiplication, because 5 (hundredths of a degree) times 5 (hundredths of a degree) is not 25 (hundredths of a degree).

e: unless you’re also tracking your units separately (25 squared hundredths of a degree)

Foxfire_
Nov 8, 2010

Yes, tracking the units separately (usually in a suffix on the variable) and then converting them as an explicitly separate step instead of part of the operation on the value. It is not at all different from what you do in a generic fixed point operation, it's just easier for me to think about.

e.g.
code:
int32_t CalculateActuatorForce(int32_t pressure_tenthPsi, int32_t bore_tenthInSq)
{
    const auto force_tenthPsiTenthInSq = pressure_tenthPsi * bore_tenthInSq;
    const auto force_tenthLbs = force_tenthPsiTenthInSq  / 10 / 10;

    return force_tenthLbs ;
}
Though honestly I haven't had to deal with it that much, usually in things I work on only one operand has units and the other is a dimensionless filter constants.

Computer viking
May 30, 2011
Now with less breakage.

Zopotantor posted:

? That's what fixed point is. You still have to scale after a multiplication, because 5 (hundredths of a degree) times 5 (hundredths of a degree) is not 25 (hundredths of a degree).

e: unless you’re also tracking your units separately (25 squared hundredths of a degree)

Five degrees times five degrees is not twenty-five degrees either, so I'm not sure how that's different?

edit: For clarity, I get that 1 dm² is not 0.1m², I'm just being difficult - and your edit does explicitly cover that.

Computer viking fucked around with this message at 15:47 on May 22, 2020

Xerophyte
Mar 17, 2008

This space intentionally left blank

Computer viking posted:

Five degrees times five degrees is not twenty-five degrees either, so I'm not sure how that's different?

Clearly the result is in stedegrees.

Dominoes
Sep 20, 2007

Like foxfire said, the natural approach there would be to keep track of units. While milli-something × milli-something doesn't map directly to something × something, you can map milli-somthing² to somthing² and do the conversions as the first and last steps.

Foxfire_
Nov 8, 2010

Somehow it's easier for me to think through:

"Approximate multiplying an integer by 1.234 using only integer math"
than
"Encode 1.234 as fixed point number, multiply it with another fixed point number, then do the scaling"

Even though both of them end up at (x * 1234)/1000 and it's exactly the same thing.

(I regret bringing it up since it's probably idiosyncratic to my brain)

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
To be clear, what Foxfire is describing is a decimal fixed point, whereas what embedded toolchains provide is usually a binary fixed point. Fixed-point vs. floating-point is technically orthogonal to binary vs. decimal, although of course if you want hardware support, you’re at the whim of your architecture design.

Fixed point is naturally great at addition and kindof punishingly bad at multiplication, especially for decimal. Fixed-point also has a code-size hit, especially if you’re using saturating math.

Floating point’s biggest advantage, besides the inherent advantages of a single type working for a lot of different applications, is that it degrades much more nicely if you get a bit outside the range you were expecting.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Binary fixed point doesn't seem especially bad at multiplication, if you have a hardware integer multiplier to leverage. Certainly no more difficult than a floating-point multiply, unless you have explicit hardware support for one but not the other.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Jabor posted:

Binary fixed point doesn't seem especially bad at multiplication, if you have a hardware integer multiplier to leverage. Certainly no more difficult than a floating-point multiply, unless you have explicit hardware support for one but not the other.

How do you deal with overflow?

Foxfire_
Nov 8, 2010

Running out of bits is a problem either way. Whether dramatic failure (fixed point overflow) or subtle failure (floating point droping precision) is worse is up to application and philosophy.

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
I'm honestly kind of surprised that floating point multiply is slower than floating point add. Being split into an exponent and mantissa seems like it should make it much easier to compute the exponent of the product than the exponent of the sum. I guess maybe it's dominated by the slowness of multiplying the mantissas? I've coded a simple hardware floating point unit (no denormalized numbers) and multiply was definitely easier to implement.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Absurd Alhazred posted:

How do you deal with overflow?



I would simply choose to never overflow. Tongue in cheek, but when I've used fixed point in the past (not often) I carefully chose the format and limited input and intermediate values to make it (hopefully!) impossible. Which was, shall we say, a bit of a headache

Qwertycoatl
Dec 31, 2008

Jeffrey of YOSPOS posted:

I'm honestly kind of surprised that floating point multiply is slower than floating point add. Being split into an exponent and mantissa seems like it should make it much easier to compute the exponent of the product than the exponent of the sum. I guess maybe it's dominated by the slowness of multiplying the mantissas? I've coded a simple hardware floating point unit (no denormalized numbers) and multiply was definitely easier to implement.

I'd naively expect a multiplier to be slower than an adder and a barrel shifter

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Qwertycoatl posted:

I'd naively expect a multiplier to be slower than an adder and a barrel shifter

Right. An FP multiplier is easier to implement given the existence of integer adders and multipliers, but an integer multiplier is just substantially more expensive to begin with.

Blotto Skorzany
Nov 7, 2008

He's a PSoC, loose and runnin'
came the whisper from each lip
And he's here to do some business with
the bad ADC on his chip
bad ADC on his chiiiiip

Absurd Alhazred posted:

How do you deal with overflow?

Dehumanize yourself and face to range analysis

krysmopompas
Jan 17, 2004
hi
Any vcpkg fans here? Similar to this question, I’m trying to figure out some sort of non-lovely way to iterate on packages. Having to go through some sort of uninstall/build/reinstall dance for any edits isn’t going to work.

Is there any way to point vcpkg at a local source tree rather than make its own copy? I’d rather manage the dependent source as submodules rather than portfiles anyhow.

If another package manager can do this, I’m not committed to vcpkg just yet.

Dren
Jan 5, 2001

Pillbug
Is there anything to help shorten the idiom of

code:
MyType a; // create an empty instance of MyType
try {
  a = MyType(1); // call constructor that could throw
  // or
  a = some_factory_func(); // call some factory func that could throw
} catch (const std::exception& e) {
  // do stuff
}
I’d like to call the constructor I want to call and initialize the variable in the outer scope (not inside the try block) all in one line.

I could do a macro w/ invoke_result to get the return type of the func but macros are hideous. Perhaps there is a slightly less hideous solution with templates.

edit: if function try blocks were allowed on lambdas that would be perfect

Dren fucked around with this message at 02:11 on May 28, 2020

qsvui
Aug 23, 2003
some crazy thing
I don't think I follow. So you want to construct an object and then initialize it? Isn't the point of a constructor to avoid two-phase initialization?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

qsvui posted:

I don't think I follow. So you want to construct an object and then initialize it? Isn't the point of a constructor to avoid two-phase initialization?

He wants to call the constructor and if it fails, handle the exception immediately, rather than having the error-handling code all the way at the bottom of the function.

One annoyance with this is that if you declare the local variable inside your try block, it goes out of scope at the end of it, so all the code that interacts with it also needs to be inside the try block - very annoying if you're trying to limit the scope of what exceptions you want to catch. So you need to declare the variable outside the try block and then do your "real" initialization inside it, which is separately annoying because now your variable can't be const.

The mediocre C++ programmers that spend way too much time answering questions on stack overflow seem to have trouble comprehending why this is something that someone would want to do, rather than showing off their esoteric knowledge of some particularly elegant and clean way of doing it, so I suspect a horrible macro might be the only way to go.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Use std::optional and emplace. You lose const; if that's a deal-breaker then you're in trouble, because you'll have to move everything logically into the initializer, which might make whatever control flow you're doing with try/catch difficult.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Dren posted:

Is there anything to help shorten the idiom of

code:
MyType a; // create an empty instance of MyType
try {
  a = MyType(1); // call constructor that could throw
  // or
  a = some_factory_func(); // call some factory func that could throw
} catch (const std::exception& e) {
  // do stuff
}
I’d like to call the constructor I want to call and initialize the variable in the outer scope (not inside the try block) all in one line.

I could do a macro w/ invoke_result to get the return type of the func but macros are hideous. Perhaps there is a slightly less hideous solution with templates.

edit: if function try blocks were allowed on lambdas that would be perfect
How dumb is this?
C++ code:
template<class T, class F, class H>
T safely_construct(F factory, H exceptionHandler)
{
	T result;
	try
	{
		result = factory();
	} catch (const std::exception& e)
	{
		exceptionHandler(e);
	}
	return result;
}
Then your first example could be:
C++ code:
	auto a = safely_construct<MyType>([]() -> { return MyType(1); }, [](const std::exception& e) -> { /* do stuff */ });

Dren
Jan 5, 2001

Pillbug

rjmccall posted:

Use std::optional and emplace. You lose const; if that's a deal-breaker then you're in trouble, because you'll have to move everything logically into the initializer, which might make whatever control flow you're doing with try/catch difficult.

Thanks, this is helpful if the type doesn't have an assignment operator. It doesn't address the annoyance of put something in the outer scope for holding the type, be that an instance of the type itself or a container like std::optional, then "really" initialize it in the inner scope. I was trying to get declaration + initialization to happen on the same line, with a catch block around it. Syntax like this would be ideal:

code:
MyType a(1, 2, 3) catch (const std::exception& e) {
  // handle e
};
Kinda like the constructor call (or just any function call) is in an implicit try block.


Absurd Alhazred posted:

How dumb is this?
C++ code:
template<class T, class F, class H>
T safely_construct(F factory, H exceptionHandler)
{
	T result;
	try
	{
		result = factory();
	} catch (const std::exception& e)
	{
		exceptionHandler(e);
	}
	return result;
}
Then your first example could be:
C++ code:
	auto a = safely_construct<MyType>([]() -> { return MyType(1); }, [](const std::exception& e) -> { /* do stuff */ });

Seems pretty close to what I'd wanted. Thanks. With a bit of modification it even lets the type be const! The lambdas are a bit verbose. The place where this one falls down syntactically is in handling specific exception types. exceptionHandler has to dynamic cast e to all the possible exception types.

Dren fucked around with this message at 16:31 on May 28, 2020

Dren
Jan 5, 2001

Pillbug
I see a problem now in the thing I want to do. Say the syntax I proposed in my last post were allowed:

code:
MyType a(1, 2, 3) catch (const std::exception& e) {
  // handle e
};
If an exception is thrown while constructing MyType, what is the state of a? It'd be undefined. Absurd Alhazred's solution (and my original idiom) fix this by requiring the type to have a default constructor. If his example is modified to be:
code:
template<class F, class H, class T=std::invoke_result_t<F>>
T safely_construct(F factory, H exceptionHandler)
{
    try
    {
        return factory();
    } catch (const std::exception& e)
    {
        exceptionHandler(e);
        return T();
    }
}
Then it works with const and has the same constraint of requiring a default constructor to exist.

edit: added invoke_result_t to get default value for T so the caller doesn't have to specify the type

The exception handler has problems though. Notably, dynamic_cast throws std::bad_cast if a cast on a reference fails.

I wonder if there's a way to provide multiple exceptionHandlers with a parameter pack and only call the best fit, kind of like what std::visit does for std::variant.

Dren fucked around with this message at 17:10 on May 28, 2020

eth0.n
Jun 1, 2012
You could have the exception handler accept a std::exception_ptr argument, then rethrow it inside the handler and dispatch to catch clauses as usual. Main downside is the exception gets thrown twice, but hopefully you aren't handling exceptions in performance critical code anyway.

There's a good example here: https://en.cppreference.com/w/cpp/error/current_exception

Dren
Jan 5, 2001

Pillbug

eth0.n posted:

You could have the exception handler accept a std::exception_ptr argument, then rethrow it inside the handler and dispatch to catch clauses as usual. Main downside is the exception gets thrown twice, but hopefully you aren't handling exceptions in performance critical code anyway.

There's a good example here: https://en.cppreference.com/w/cpp/error/current_exception

That's pretty good. Unfortunately, even in its best iterations, this thing is less syntax sugar and more obfuscation. I cannot bring myself to put it into any production code seeing as the whole point was to make things more expressive and improve readability.

Foxfire_
Nov 8, 2010

I'm unclear on what exactly you're trying to accomplish. Construct an object, but if its constructor throws, replace it with a default-constructed one + run some error handling code?

code:
template <typename RTYPE,
          typename CONSTRUCTION_LAMBDA_TYPE,
          typename ERROR_LAMBDA_TYPE>
RTYPE Wrapper(CONSTRUCTION_LAMBDA_TYPE constructorExpression, ERROR_LAMBDA_TYPE errorExpression)
{
    try
    {
        return constructorExpression();
    }
    catch (const std::exception& e)
    {
        errorExpression();
    }
    return RTYPE();
}

void Foo()
{
  Thing a = Wrapper<Thing>(
        []{ return Thing(1,2,3,4); },
        []{ printf("There was an error\n"); });
}

?


(exceptions were a mistake)

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
Constructors cannot fail, they can only be failed.

Foxfire_ posted:

(exceptions were a mistake)
+1

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Oh man, I was figuring you were going to do something like return an error code if the construction failed. If you don't want to affect control flow - instead just make it a different value plus maybe run some side-effecting code - then it's probably quite doable to have a perfect forwarding template that accepts an extra lambda to call when the constructor throws.

Dren
Jan 5, 2001

Pillbug

Foxfire_ posted:

I'm unclear on what exactly you're trying to accomplish. Construct an object, but if its constructor throws, replace it with a default-constructed one + run some error handling code?

code:
template <typename RTYPE,
          typename CONSTRUCTION_LAMBDA_TYPE,
          typename ERROR_LAMBDA_TYPE>
RTYPE Wrapper(CONSTRUCTION_LAMBDA_TYPE constructorExpression, ERROR_LAMBDA_TYPE errorExpression)
{
    try
    {
        return constructorExpression();
    }
    catch (const std::exception& e)
    {
        errorExpression();
    }
    return RTYPE();
}

void Foo()
{
  Thing a = Wrapper<Thing>(
        []{ return Thing(1,2,3,4); },
        []{ printf("There was an error\n"); });
}

?


(exceptions were a mistake)

That one is quite similar to what Absurd Alhazred posted. It does part of what I wanted and I posted an improvement to it where RTYPE is deduced so the callee doesn’t have to supply it.

What I really want is to be able to declare and init a variable, where the init function or constructor might throw, and be able to handle any exceptions without separating the declaration and init statements. And I want the resulting syntax to be less work than this (or at least close to it):
code:
MyType a;
try {
  a = MyType(1, 2, 3, 4);
} catch (const error& e) {
  // do stuff
}
The problem with the solutions where the template function catches std::exception is that handing multiple exception types becomes gross. Capturing an exception_ptr and rethrowing it into a try-catch block that specifies the regular exception syntax was proposed to fix that, and it does, but it’s quite a bit more work for the user than the original syntax.

Dominoes
Sep 20, 2007

Hey dudes. What's the proper way to insert a class in a constructor by calling its constructor of the same name? Module in question

The ADS1115 class I'm using as a template, since it works.

C++
C code:
MyClass::MyClass() {
    ADS1115 ads1115(0x48); 

    const float e_mea = 0.01;
    const float e_est = 3.0;
    const float q = .005 * .005;
    SimpleKalmanFilter kf(e_mea, e_est, q);

    adc = ads1115;
    filter = kf;
}
Header
C code:
class MyClass {
public:
     MyClass();
     SimpleKalmanFilter filter;

private:
   ADS1115 adc;
};
Is there something obvious I'm doing wrong? I'm overall shaky on declaration/constructor etc syntax. It's simple enough for primitive types, but gets hazy when dealing with types that share the same name as their constructor. Ie, is SimpleKalmanFilter a type, a constructor method, or both?

The error, using Arduino IDE:
Bash code:



...\MyModule.cpp: In constructor 'MyClass::MyClass()':

...MyModule.cpp:44:20: error: no matching function for call to 'SimpleKalmanFilter::SimpleKalmanFilter()'

 'MyClass::'MyClass() {

                    ^

In file included from ...MyModule.cpp:12:0:

.../SimpleKalmanFilter.h:14:3: note: candidate: SimpleKalmanFilter::SimpleKalmanFilter(float, float, float)

   SimpleKalmanFilter(float mea_e, float est_e, float q);

   ^~~~~~~~~~~~~~~~~~

.../SimpleKalmanFilter.h:14:3: note:   candidate expects 3 arguments, 0 provided
Based on the soln to a prev question I had here, I think I need to initialize SimpleKalmanFilter.

Dominoes fucked around with this message at 17:39 on May 29, 2020

Xarn
Jun 26, 2015
Yeah, if the member does not have a default-constructor, then it has to be initialized in the init list:

C++ code:
MyClass::MyClass():
    adc(0x48),
    filter(e_mea, e_est, q) {
    // constructor body
}
You will also need to declare the parameters e_mea, e_est, q before that... e.g. by making them TU-local constants.

Dominoes
Sep 20, 2007

What would that look like in the full context of this?

C++ code:
MyClass::MyClass() {
    ADS1115 ads1115(0x48); 

    const float e_mea = 0.01;
    const float e_est = 3.0;
    const float q = .005 * .005;
    SimpleKalmanFilter kf = SimpleKalmanFilter(e_mea, e_est, q);

    adc = ads1115;
    filter = kf;
}
An attempt:
C++ code:
MyClass::MyClass() {
    ADS1115 ads1115(0x48); 

    // Initialize `filter` field ??
    filter(e_mea, e_est, q);

    const float e_mea = 0.01;
    const float e_est = 3.0;
    const float q = .005 * .005;

    // Create var `kf`of type `SimpleKalmanFilter` using constructor `SimpleKalmanFilter`
    SimpleKalmanFilter kf = SimpleKalmanFilter(e_mea, e_est, q); 

    // The LHS  below are field names. Assign local variables to them. Set up must be done before this point.
    adc = ads1115;
    filter = kf;
}
I did notice some unexpected behavior, despite compiling, when I tried initialized the adc field directly instead of setting it up as a var before. (Before adding the filter field, which is triggering the compile errors)

Dominoes fucked around with this message at 18:35 on May 29, 2020

Xarn
Jun 26, 2015
C++ code:
static const float e_mea = 0.01;
static const float e_est = 3.0;
static const float q = .005 * .005;

MyClass::MyClass(): // <-- this colon is important
    adc(0x48),
    filter(e_mea, e_est, q)
    {}

Dominoes
Sep 20, 2007

Thank you very much! Works perfectly, and I learned a new syntax pattern.

Foxfire_
Nov 8, 2010

Marked up with what stuff means:
C code:

// Create a new type named 'MyClass'
class MyClass 
{
// Stuff after this label will be accessible from outside
public:

    // Because the function name matches the type name, this is a constructor for the type
    // It takes no arguments, so it is the default constructor and will be called when you do something like
    //
    // Also it has no return type because it is a constructor
    // MyClass foo;
    //
     MyClass();

    // This is a variable declaration, not a function (no parenthesis)
    // 
    // The declaration is the same for a simple built in type like 'int'
    //
    // The type is 'SimpleKalmanFilter'.  The name of this instance is 'filter'
    // [analagous to 'int blah' with type 'int' and name 'blah'
    //
    // In general, you should not be making publicly accessible variables in your classes.
    // Usually you will expose functions only and whatever data is needed to implement those
    // operations should be private
     SimpleKalmanFilter filter;

// Stuff below here is internal access only
private:

   // Another variable declaration (no parenthesis).  Reading it is the same as the othes
   // Type is 'ADS1115', name is 'adc'
   ADS1115 adc;
};
All of this is declarations, not implementation. We've told the compiler enough that
it can figure out how much memory to reserve for an instance of MyClass, but nothing
about how to implement the functions in it.

Presumably later in another file, you have something like:

code:
// Here you are providing an implementation for a function named 'MyClass' within 
// the 'MyClass' type.  It again has no return type because it is a constructor
//
// Because this is a constructor, a few things will happen when it is used
// 1. Memory is allocated by something outside the stack.  i.e. stack pointer incremented,
//     memory taken from heap, there is a global, etc...  The contents are initially undefined
//     and may be any random junk
//
// 2. All of the member variables for the class are constructed.  For some types,
//     this may do nothing (i.e. constructing an 'int' doesn't do anything and the memory for
//    it is still undefined).  For complicated types, this will involve constructor calls.
//  
//    For this code, the 'filter' variable and 'adc' variable will have their constructors called.
//    This is done in the same order that they're declared in the class declaration (filter first,
//    then adc).
//
// 3. The body of the MyClass constructor is called.  
MyClass::MyClass()
// The member variable constructors are determined here in an initializer list.
// The syntax is:
//
// :                     <-- a colon
// <variable name>()    <-- name of variable being initialized, followed by arguments for its constructor
//
// In modern C++, using a {} expression instead of a () is also fine
//
// If you do not specify anything for a variable, it is default constructed.  If the type has no default
// constructor, it is a compile error [this is what is happening to you, 'SimpleKalmanFilter' has no
// default constructor, e.g. you can't do 'SimpleKalmanFilter filter;' in other code, it requires arguments
//
// If you specify these out-of-order, they are run in the order from the class declaration and you ought
// to get a warning.  If you specify some but not all, you also ought to get a warning.
: filter(),
  adc()
{
    // Code in here will be run after all of the member variables have been constructed.
}

Spatial
Nov 15, 2007

You can put static float constants in the class declaration in modern C++.

code:
class Fart {
    static constexpr float methaneDensity = 1.23f;
    static constexpr float cheekContact   = 1.99f;
};

Adbot
ADBOT LOVES YOU

Dominoes
Sep 20, 2007

Foxfire - thank you very much! I've saved your example locally as a reference to come back to whenever initializing fields and setting up classes/structs.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply